@scality/data-browser-library 1.0.6 → 1.0.8

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.
@@ -3,14 +3,19 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
3
  import user_event from "@testing-library/user-event";
4
4
  import { MemoryRouter, Route, Routes } from "react-router";
5
5
  import { useGetBucketLifecycle, useSetBucketLifecycle } from "../../hooks/bucketConfiguration.js";
6
+ import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
6
7
  import { createTestWrapper, findToggleByLabel, mockErrorSubmit, mockOffsetSize, mockSuccessSubmit, submitForm } from "../../test/testUtils.js";
7
8
  import { BucketLifecycleFormPage } from "../buckets/BucketLifecycleFormPage.js";
8
9
  jest.mock('../../hooks/bucketConfiguration', ()=>({
9
10
  useGetBucketLifecycle: jest.fn(),
10
11
  useSetBucketLifecycle: jest.fn()
11
12
  }));
13
+ jest.mock('../../hooks/useISVBucketDetection', ()=>({
14
+ useISVBucketStatus: jest.fn()
15
+ }));
12
16
  const mockUseGetBucketLifecycle = jest.mocked(useGetBucketLifecycle);
13
17
  const mockUseSetBucketLifecycle = jest.mocked(useSetBucketLifecycle);
18
+ const mockUseISVBucketStatus = jest.mocked(useISVBucketStatus);
14
19
  const mockNavigate = jest.fn();
15
20
  const mockShowToast = jest.fn();
16
21
  jest.mock('react-router', ()=>({
@@ -69,6 +74,15 @@ describe('BucketLifecycleFormPage', ()=>{
69
74
  mutate: mockMutate,
70
75
  isPending: false
71
76
  });
77
+ mockUseISVBucketStatus.mockReturnValue({
78
+ isVeeamBucket: false,
79
+ isCommvaultBucket: false,
80
+ isKastenBucket: false,
81
+ isISVManaged: false,
82
+ isvApplication: void 0,
83
+ isLoading: false,
84
+ bucketTagsStatus: 'success'
85
+ });
72
86
  });
73
87
  describe('Page Rendering', ()=>{
74
88
  it('renders create mode with correct title', ()=>{
@@ -615,4 +629,62 @@ describe('BucketLifecycleFormPage', ()=>{
615
629
  });
616
630
  });
617
631
  });
632
+ describe('ISV Bucket Warning', ()=>{
633
+ const setupISVMock = (isvApplication = 'Veeam')=>{
634
+ mockUseISVBucketStatus.mockReturnValue({
635
+ isVeeamBucket: 'Veeam' === isvApplication,
636
+ isCommvaultBucket: 'Commvault' === isvApplication,
637
+ isKastenBucket: 'Kasten' === isvApplication,
638
+ isISVManaged: true,
639
+ isvApplication,
640
+ isLoading: false,
641
+ bucketTagsStatus: 'success'
642
+ });
643
+ };
644
+ it('renders ISV warning banner and checkbox when bucket is ISV-managed', ()=>{
645
+ setupISVMock('Veeam');
646
+ renderBucketLifecycleFormPage();
647
+ expect(screen.getByText(/bucket used for external integration with Veeam/i)).toBeInTheDocument();
648
+ expect(screen.getByText(/may conflict with Veeam's backup strategy/i)).toBeInTheDocument();
649
+ expect(screen.getByLabelText(/i understand what i'm doing/i)).toBeInTheDocument();
650
+ });
651
+ it('does not render ISV warning for non-ISV buckets', ()=>{
652
+ renderBucketLifecycleFormPage();
653
+ expect(screen.queryByText(/bucket used for external integration with/i)).not.toBeInTheDocument();
654
+ expect(screen.queryByLabelText(/i understand what i'm doing/i)).not.toBeInTheDocument();
655
+ });
656
+ it('blocks form submission when ISV checkbox is unchecked', async ()=>{
657
+ setupISVMock('Veeam');
658
+ renderBucketLifecycleFormPage();
659
+ await fillRequiredFields('isv-rule');
660
+ await waitFor(()=>{
661
+ const createButton = screen.getByRole('button', {
662
+ name: /create/i
663
+ });
664
+ expect(createButton).toBeDisabled();
665
+ });
666
+ });
667
+ it('allows form submission when ISV checkbox is checked', async ()=>{
668
+ setupISVMock('Veeam');
669
+ renderBucketLifecycleFormPage();
670
+ await fillRequiredFields('isv-rule');
671
+ const checkbox = screen.getByLabelText(/i understand what i'm doing/i);
672
+ await user_event.click(checkbox);
673
+ mockSuccessSubmit(mockMutate);
674
+ await submitForm('create');
675
+ await waitFor(()=>{
676
+ expect(mockMutate).toHaveBeenCalled();
677
+ });
678
+ });
679
+ it('displays correct application name for Commvault ISV bucket', ()=>{
680
+ setupISVMock('Commvault');
681
+ renderBucketLifecycleFormPage();
682
+ expect(screen.getByText(/bucket used for external integration with Commvault/i)).toBeInTheDocument();
683
+ });
684
+ it('displays correct application name for Kasten ISV bucket', ()=>{
685
+ setupISVMock('Kasten');
686
+ renderBucketLifecycleFormPage();
687
+ expect(screen.getByText(/bucket used for external integration with Kasten/i)).toBeInTheDocument();
688
+ });
689
+ });
618
690
  });
@@ -73,6 +73,7 @@ describe('BucketPolicyPage', ()=>{
73
73
  mockUseISVBucketStatus.mockReturnValue({
74
74
  isVeeamBucket: false,
75
75
  isCommvaultBucket: false,
76
+ isKastenBucket: false,
76
77
  isISVManaged: false,
77
78
  isvApplication: void 0,
78
79
  isLoading: false,
@@ -204,6 +205,7 @@ describe('BucketPolicyPage', ()=>{
204
205
  mockUseISVBucketStatus.mockReturnValue({
205
206
  isVeeamBucket: false,
206
207
  isCommvaultBucket: false,
208
+ isKastenBucket: false,
207
209
  isISVManaged: false,
208
210
  isvApplication: void 0,
209
211
  isLoading: true,
@@ -217,6 +219,7 @@ describe('BucketPolicyPage', ()=>{
217
219
  mockUseISVBucketStatus.mockReturnValue({
218
220
  isVeeamBucket: true,
219
221
  isCommvaultBucket: false,
222
+ isKastenBucket: false,
220
223
  isISVManaged: true,
221
224
  isvApplication: 'Veeam',
222
225
  isLoading: false,
@@ -234,6 +237,7 @@ describe('BucketPolicyPage', ()=>{
234
237
  mockUseISVBucketStatus.mockReturnValue({
235
238
  isVeeamBucket: false,
236
239
  isCommvaultBucket: true,
240
+ isKastenBucket: false,
237
241
  isISVManaged: true,
238
242
  isvApplication: 'Commvault',
239
243
  isLoading: false,
@@ -251,6 +255,7 @@ describe('BucketPolicyPage', ()=>{
251
255
  mockUseISVBucketStatus.mockReturnValue({
252
256
  isVeeamBucket: false,
253
257
  isCommvaultBucket: false,
258
+ isKastenBucket: false,
254
259
  isISVManaged: false,
255
260
  isvApplication: void 0,
256
261
  isLoading: false,
@@ -4,6 +4,7 @@ import user_event from "@testing-library/user-event";
4
4
  import { MemoryRouter, Route, Routes } from "react-router";
5
5
  import { useGetBucketReplication, useSetBucketReplication } from "../../hooks/bucketConfiguration.js";
6
6
  import { useBuckets } from "../../hooks/bucketOperations.js";
7
+ import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
7
8
  import { createTestWrapper, findToggleByLabel, mockErrorSubmit, mockOffsetSize, mockSuccessSubmit, submitForm } from "../../test/testUtils.js";
8
9
  import { BucketReplicationFormPage } from "../buckets/BucketReplicationFormPage.js";
9
10
  jest.mock('../../hooks/bucketConfiguration', ()=>({
@@ -13,9 +14,13 @@ jest.mock('../../hooks/bucketConfiguration', ()=>({
13
14
  jest.mock('../../hooks/bucketOperations', ()=>({
14
15
  useBuckets: jest.fn()
15
16
  }));
17
+ jest.mock('../../hooks/useISVBucketDetection', ()=>({
18
+ useISVBucketStatus: jest.fn()
19
+ }));
16
20
  const mockUseGetBucketReplication = jest.mocked(useGetBucketReplication);
17
21
  const mockUseSetBucketReplication = jest.mocked(useSetBucketReplication);
18
22
  const mockUseBuckets = jest.mocked(useBuckets);
23
+ const mockUseISVBucketStatus = jest.mocked(useISVBucketStatus);
19
24
  const mockNavigate = jest.fn();
20
25
  const mockShowToast = jest.fn();
21
26
  jest.mock('react-router', ()=>({
@@ -96,6 +101,15 @@ describe('BucketReplicationFormPage', ()=>{
96
101
  },
97
102
  status: 'success'
98
103
  });
104
+ mockUseISVBucketStatus.mockReturnValue({
105
+ isVeeamBucket: false,
106
+ isCommvaultBucket: false,
107
+ isKastenBucket: false,
108
+ isISVManaged: false,
109
+ isvApplication: void 0,
110
+ isLoading: false,
111
+ bucketTagsStatus: 'success'
112
+ });
99
113
  });
100
114
  describe('Page Rendering', ()=>{
101
115
  it('renders create mode with correct title', ()=>{
@@ -1876,4 +1890,66 @@ describe('BucketReplicationFormPage', ()=>{
1876
1890
  });
1877
1891
  });
1878
1892
  });
1893
+ describe('ISV Bucket Warning', ()=>{
1894
+ const setupISVMock = (isvApplication = 'Veeam')=>{
1895
+ mockUseISVBucketStatus.mockReturnValue({
1896
+ isVeeamBucket: 'Veeam' === isvApplication,
1897
+ isCommvaultBucket: 'Commvault' === isvApplication,
1898
+ isKastenBucket: 'Kasten' === isvApplication,
1899
+ isISVManaged: true,
1900
+ isvApplication,
1901
+ isLoading: false,
1902
+ bucketTagsStatus: 'success'
1903
+ });
1904
+ };
1905
+ it('renders ISV warning banner and checkbox when bucket is ISV-managed', ()=>{
1906
+ setupISVMock('Veeam');
1907
+ renderBucketReplicationFormPage();
1908
+ expect(screen.getByText(/bucket used for external integration with Veeam/i)).toBeInTheDocument();
1909
+ expect(screen.getByText(/rendering the replicated data unusable for recovery/i)).toBeInTheDocument();
1910
+ expect(screen.getByLabelText(/i understand what i'm doing/i)).toBeInTheDocument();
1911
+ });
1912
+ it('does not render ISV warning for non-ISV buckets', ()=>{
1913
+ renderBucketReplicationFormPage();
1914
+ expect(screen.queryByText(/bucket used for external integration with/i)).not.toBeInTheDocument();
1915
+ expect(screen.queryByLabelText(/i understand what i'm doing/i)).not.toBeInTheDocument();
1916
+ });
1917
+ it('blocks form submission when ISV checkbox is unchecked', async ()=>{
1918
+ setupISVMock('Veeam');
1919
+ renderBucketReplicationFormPage();
1920
+ await fillRequiredFields({
1921
+ ruleId: 'isv-rule'
1922
+ });
1923
+ await waitFor(()=>{
1924
+ const createButton = screen.getByRole('button', {
1925
+ name: /create/i
1926
+ });
1927
+ expect(createButton).toBeDisabled();
1928
+ });
1929
+ });
1930
+ it('allows form submission when ISV checkbox is checked', async ()=>{
1931
+ setupISVMock('Veeam');
1932
+ renderBucketReplicationFormPage();
1933
+ await fillRequiredFields({
1934
+ ruleId: 'isv-rule'
1935
+ });
1936
+ const checkbox = screen.getByLabelText(/i understand what i'm doing/i);
1937
+ await user_event.click(checkbox);
1938
+ mockSuccessSubmit(mockMutate);
1939
+ await submitForm('create');
1940
+ await waitFor(()=>{
1941
+ expect(mockMutate).toHaveBeenCalled();
1942
+ });
1943
+ });
1944
+ it('displays correct application name for Commvault ISV bucket', ()=>{
1945
+ setupISVMock('Commvault');
1946
+ renderBucketReplicationFormPage();
1947
+ expect(screen.getByText(/bucket used for external integration with Commvault/i)).toBeInTheDocument();
1948
+ });
1949
+ it('displays correct application name for Kasten ISV bucket', ()=>{
1950
+ setupISVMock('Kasten');
1951
+ renderBucketReplicationFormPage();
1952
+ expect(screen.getByText(/bucket used for external integration with Kasten/i)).toBeInTheDocument();
1953
+ });
1954
+ });
1879
1955
  });
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { joiResolver } from "@hookform/resolvers/joi";
3
- import { Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
3
+ import { Banner, Checkbox, Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
4
4
  import { convertRemToPixels } from "@scality/core-ui/dist/components/tablev2/TableUtils";
5
5
  import { Box, Button, Input, Select } from "@scality/core-ui/dist/next";
6
6
  import joi from "joi";
@@ -9,6 +9,7 @@ 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";
12
13
  import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
13
14
  import { AWS_RULE_LIMITS, STATUS_OPTIONS, buildS3Filter } from "../../utils/s3RuleUtils.js";
14
15
  import { ArrayFieldActions } from "../ui/ArrayFieldActions.js";
@@ -151,6 +152,9 @@ const createSchema = (hasCustomStorageClassSelector)=>{
151
152
  'number.min': `Days must be at least ${LIFECYCLE_LIMITS.ABORT_MPU_MIN_DAYS}`
152
153
  }),
153
154
  otherwise: joi.any()
155
+ }),
156
+ understandISVRisk: joi.boolean().invalid(false).messages({
157
+ 'any.invalid': 'You must acknowledge the risk'
154
158
  })
155
159
  }).custom((value, helpers)=>{
156
160
  const hasAtLeastOneAction = value.transitionsEnabled || value.expirationEnabled || value.expiredObjectDeleteMarker || value.noncurrentTransitionsEnabled || value.noncurrentExpirationEnabled || value.abortMpuEnabled;
@@ -291,6 +295,7 @@ function BucketLifecycleFormPage() {
291
295
  const navigate = useDataBrowserNavigate();
292
296
  const { showToast } = useToast();
293
297
  const isEditMode = !!ruleId;
298
+ const { isISVManaged, isvApplication, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
294
299
  const { data: lifecycleData, status: lifecycleStatus } = useGetBucketLifecycle({
295
300
  Bucket: bucketName
296
301
  });
@@ -338,7 +343,8 @@ function BucketLifecycleFormPage() {
338
343
  noncurrentExpirationDays: 30,
339
344
  noncurrentNewerVersions: 0,
340
345
  abortMpuEnabled: false,
341
- abortMpuDays: 7
346
+ abortMpuDays: 7,
347
+ understandISVRisk: true
342
348
  }
343
349
  });
344
350
  const { handleSubmit, register, control, watch, reset, formState: { isValid, isDirty, errors } } = methods;
@@ -391,6 +397,14 @@ function BucketLifecycleFormPage() {
391
397
  existingRule,
392
398
  reset
393
399
  ]);
400
+ useEffect(()=>{
401
+ methods.setValue('understandISVRisk', !isISVManaged, {
402
+ shouldValidate: true
403
+ });
404
+ }, [
405
+ isISVManaged,
406
+ methods
407
+ ]);
394
408
  const prevTransitionsEnabledRef = useRef(null);
395
409
  const prevNoncurrentTransitionsEnabledRef = useRef(null);
396
410
  useEffect(()=>{
@@ -532,7 +546,7 @@ function BucketLifecycleFormPage() {
532
546
  isEditMode,
533
547
  existingRule
534
548
  ]);
535
- if ('pending' === lifecycleStatus) return /*#__PURE__*/ jsx(Loader, {
549
+ if ('pending' === lifecycleStatus || isISVLoading) return /*#__PURE__*/ jsx(Loader, {
536
550
  centered: true,
537
551
  children: /*#__PURE__*/ jsx(Text, {
538
552
  children: "Loading..."
@@ -578,6 +592,39 @@ function BucketLifecycleFormPage() {
578
592
  ]
579
593
  }),
580
594
  children: [
595
+ isISVManaged && /*#__PURE__*/ jsxs(Stack, {
596
+ direction: "vertical",
597
+ gap: "r16",
598
+ children: [
599
+ /*#__PURE__*/ jsxs(Banner, {
600
+ variant: "warning",
601
+ title: `Bucket used for external integration with
602
+ ${isvApplication}`,
603
+ icon: /*#__PURE__*/ jsx(Icon, {
604
+ color: "statusWarning",
605
+ name: "Exclamation-circle"
606
+ }),
607
+ children: [
608
+ "This bucket was provisioned specifically for ",
609
+ isvApplication,
610
+ ", which manages its own data retention.",
611
+ /*#__PURE__*/ jsx("br", {}),
612
+ "Configuring manual lifecycle rules here may conflict with ",
613
+ isvApplication,
614
+ "'s backup strategy, potentially leading to data corruption or unintended deletion."
615
+ ]
616
+ }),
617
+ /*#__PURE__*/ jsx(Controller, {
618
+ control: control,
619
+ name: "understandISVRisk",
620
+ render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Checkbox, {
621
+ label: "I understand what I'm doing",
622
+ checked: !!value,
623
+ onChange: (e)=>onChange(e.target.checked)
624
+ })
625
+ })
626
+ ]
627
+ }),
581
628
  /*#__PURE__*/ jsxs(FormSection, {
582
629
  title: {
583
630
  name: 'Rule Scope'
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { joiResolver } from "@hookform/resolvers/joi";
3
- import { Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
3
+ import { Banner, Checkbox, Form, FormGroup, FormSection, Icon, Loader, Stack, Text, Toggle, spacing, useToast } from "@scality/core-ui";
4
4
  import { convertRemToPixels } from "@scality/core-ui/dist/components/tablev2/TableUtils";
5
5
  import { Box, Button, Input, Select } from "@scality/core-ui/dist/next";
6
6
  import joi from "joi";
@@ -10,6 +10,7 @@ import { useParams } from "react-router";
10
10
  import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
11
11
  import { useGetBucketReplication, useSetBucketReplication } from "../../hooks/bucketConfiguration.js";
12
12
  import { useBuckets } from "../../hooks/bucketOperations.js";
13
+ import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
13
14
  import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
14
15
  import { AWS_RULE_LIMITS, STATUS_OPTIONS, buildS3Filter } from "../../utils/s3RuleUtils.js";
15
16
  import { FilterFormSection, createFilterValidationSchema } from "../ui/FilterFormSection.js";
@@ -94,7 +95,10 @@ const createSchema = (hasExistingRules)=>joi.object({
94
95
  otherwise: joi.boolean()
95
96
  }),
96
97
  deleteMarkerReplication: joi.boolean(),
97
- switchObjectOwnership: joi.boolean()
98
+ switchObjectOwnership: joi.boolean(),
99
+ understandISVRisk: joi.boolean().invalid(false).messages({
100
+ 'any.invalid': 'You must acknowledge the risk'
101
+ })
98
102
  });
99
103
  const ruleToFormValues = (rule, role)=>{
100
104
  const formValues = {
@@ -252,6 +256,7 @@ function BucketReplicationFormPage() {
252
256
  const navigate = useDataBrowserNavigate();
253
257
  const { showToast } = useToast();
254
258
  const isEditMode = !!ruleId;
259
+ const { isISVManaged, isvApplication, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
255
260
  const { data: replicationData, status: replicationStatus } = useGetBucketReplication({
256
261
  Bucket: bucketName
257
262
  });
@@ -312,7 +317,8 @@ function BucketReplicationFormPage() {
312
317
  enforceRTC: false,
313
318
  enableRTCNotification: false,
314
319
  deleteMarkerReplication: false,
315
- switchObjectOwnership: false
320
+ switchObjectOwnership: false,
321
+ understandISVRisk: true
316
322
  }
317
323
  });
318
324
  const { handleSubmit, register, control, watch, reset, formState: { isValid, isDirty, errors } } = methods;
@@ -403,6 +409,14 @@ function BucketReplicationFormPage() {
403
409
  enforceRTC,
404
410
  methods
405
411
  ]);
412
+ useEffect(()=>{
413
+ methods.setValue('understandISVRisk', !isISVManaged, {
414
+ shouldValidate: true
415
+ });
416
+ }, [
417
+ isISVManaged,
418
+ methods
419
+ ]);
406
420
  const handleCancel = useCallback(()=>{
407
421
  navigate(`/buckets/${bucketName}?tab=replication`);
408
422
  }, [
@@ -450,7 +464,7 @@ function BucketReplicationFormPage() {
450
464
  isEditMode,
451
465
  existingRule
452
466
  ]);
453
- if ('pending' === replicationStatus) return /*#__PURE__*/ jsx(Loader, {
467
+ if ('pending' === replicationStatus || isISVLoading) return /*#__PURE__*/ jsx(Loader, {
454
468
  centered: true,
455
469
  children: /*#__PURE__*/ jsx(Text, {
456
470
  children: "Loading..."
@@ -496,6 +510,38 @@ function BucketReplicationFormPage() {
496
510
  ]
497
511
  }),
498
512
  children: [
513
+ isISVManaged && /*#__PURE__*/ jsxs(Stack, {
514
+ direction: "vertical",
515
+ gap: "r16",
516
+ children: [
517
+ /*#__PURE__*/ jsxs(Banner, {
518
+ variant: "warning",
519
+ title: `Bucket used for external integration with ${isvApplication}`,
520
+ icon: /*#__PURE__*/ jsx(Icon, {
521
+ color: "statusWarning",
522
+ name: "Exclamation-circle"
523
+ }),
524
+ children: [
525
+ "This bucket was provisioned specifically for ",
526
+ isvApplication,
527
+ ". Manual replication workflows created here may be redundant or invisible to the application, rendering the replicated data unusable for recovery.",
528
+ /*#__PURE__*/ jsx("br", {}),
529
+ "To ensure data consistency, manage all replication tasks directly within ",
530
+ isvApplication,
531
+ "."
532
+ ]
533
+ }),
534
+ /*#__PURE__*/ jsx(Controller, {
535
+ control: control,
536
+ name: "understandISVRisk",
537
+ render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Checkbox, {
538
+ label: "I understand what I'm doing",
539
+ checked: !!value,
540
+ onChange: (e)=>onChange(e.target.checked)
541
+ })
542
+ })
543
+ ]
544
+ }),
499
545
  /*#__PURE__*/ jsx(FormSection, {
500
546
  title: {
501
547
  name: 'Role'
@@ -54,6 +54,7 @@ const mockHookDefaults = ()=>{
54
54
  mockUseISVBucketStatus.mockReturnValue({
55
55
  isVeeamBucket: false,
56
56
  isCommvaultBucket: false,
57
+ isKastenBucket: false,
57
58
  isISVManaged: false,
58
59
  isvApplication: void 0,
59
60
  isLoading: false,
@@ -141,6 +142,7 @@ describe('BucketVersioning', ()=>{
141
142
  mockUseISVBucketStatus.mockReturnValue({
142
143
  isVeeamBucket: true,
143
144
  isCommvaultBucket: false,
145
+ isKastenBucket: false,
144
146
  isISVManaged: true,
145
147
  isvApplication: 'Veeam',
146
148
  isLoading: false,
@@ -5,7 +5,7 @@ import { cleanup, render, screen } from "@testing-library/react";
5
5
  import { MemoryRouter, Route, Routes } from "react-router";
6
6
  import { DataBrowserUICustomizationProvider } from "../../../../contexts/DataBrowserUICustomizationContext.js";
7
7
  var __webpack_modules__ = {
8
- ".." (module) {
8
+ "../index" (module) {
9
9
  module.exports = __rspack_external__index_js_95fdb65a;
10
10
  }
11
11
  };
@@ -19,7 +19,7 @@ function __webpack_require__(moduleId) {
19
19
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
20
20
  return module.exports;
21
21
  }
22
- var external_index_js_ = __webpack_require__("..");
22
+ var external_index_js_ = __webpack_require__("../index");
23
23
  jest.mock('../ObjectSummary', ()=>({
24
24
  ObjectSummary: ()=>/*#__PURE__*/ jsx("div", {
25
25
  "data-testid": "object-summary",
@@ -398,7 +398,7 @@ describe('ObjectDetails', ()=>{
398
398
  expect(screen.getByTestId('object-summary')).toBeInTheDocument();
399
399
  });
400
400
  it('should throw error when useObjectDetailsContext is used outside provider', ()=>{
401
- const { useObjectDetailsContext } = __webpack_require__("..");
401
+ const { useObjectDetailsContext } = __webpack_require__("../index");
402
402
  const TestComponent = ()=>{
403
403
  useObjectDetailsContext();
404
404
  return null;
@@ -33,6 +33,7 @@ beforeEach(()=>{
33
33
  mockUseISVBucketStatus.mockReturnValue({
34
34
  isVeeamBucket: false,
35
35
  isCommvaultBucket: false,
36
+ isKastenBucket: false,
36
37
  isISVManaged: false,
37
38
  isvApplication: void 0,
38
39
  isLoading: false,
@@ -73,6 +74,7 @@ it('is disabled for Veeam Backup & Replication buckets', ()=>{
73
74
  mockUseISVBucketStatus.mockReturnValue({
74
75
  isVeeamBucket: true,
75
76
  isCommvaultBucket: false,
77
+ isKastenBucket: false,
76
78
  isISVManaged: true,
77
79
  isvApplication: 'Veeam',
78
80
  isLoading: false,
@@ -88,6 +90,7 @@ it('is disabled for Veeam Office 365 v6/v7 buckets', ()=>{
88
90
  mockUseISVBucketStatus.mockReturnValue({
89
91
  isVeeamBucket: true,
90
92
  isCommvaultBucket: false,
93
+ isKastenBucket: false,
91
94
  isISVManaged: true,
92
95
  isvApplication: 'Veeam',
93
96
  isLoading: false,
@@ -103,6 +106,7 @@ it('is disabled for Veeam Office 365 v8+ buckets', ()=>{
103
106
  mockUseISVBucketStatus.mockReturnValue({
104
107
  isVeeamBucket: true,
105
108
  isCommvaultBucket: false,
109
+ isKastenBucket: false,
106
110
  isISVManaged: true,
107
111
  isvApplication: 'Veeam',
108
112
  isLoading: false,
@@ -118,6 +122,7 @@ it('is disabled for Commvault buckets', ()=>{
118
122
  mockUseISVBucketStatus.mockReturnValue({
119
123
  isVeeamBucket: false,
120
124
  isCommvaultBucket: true,
125
+ isKastenBucket: false,
121
126
  isISVManaged: true,
122
127
  isvApplication: 'Commvault',
123
128
  isLoading: false,
@@ -133,6 +138,7 @@ it('is disabled for ISV buckets tagged as Veeam Backup for Microsoft 365', ()=>{
133
138
  mockUseISVBucketStatus.mockReturnValue({
134
139
  isVeeamBucket: true,
135
140
  isCommvaultBucket: false,
141
+ isKastenBucket: false,
136
142
  isISVManaged: true,
137
143
  isvApplication: 'Veeam',
138
144
  isLoading: false,
@@ -148,6 +154,7 @@ it('is disabled for ISV buckets tagged as Veeam Backup & Replication', ()=>{
148
154
  mockUseISVBucketStatus.mockReturnValue({
149
155
  isVeeamBucket: true,
150
156
  isCommvaultBucket: false,
157
+ isKastenBucket: false,
151
158
  isISVManaged: true,
152
159
  isvApplication: 'Veeam',
153
160
  isLoading: false,
@@ -191,6 +198,7 @@ it('shows loading state when fetching bucket tags', ()=>{
191
198
  mockUseISVBucketStatus.mockReturnValue({
192
199
  isVeeamBucket: false,
193
200
  isCommvaultBucket: false,
201
+ isKastenBucket: false,
194
202
  isISVManaged: false,
195
203
  isvApplication: void 0,
196
204
  isLoading: true,
@@ -9,7 +9,7 @@ const DropZone = styled_components.div`
9
9
  flex: 1;
10
10
  display: flex;
11
11
  flex-direction: column;
12
- height: 300px;
12
+ height: 400px;
13
13
  width: 500px;
14
14
  padding: ${spacing.r20};
15
15
  border-width: ${spacing.r2};
@@ -18,8 +18,10 @@ const DropZone = styled_components.div`
18
18
  border-style: dashed;
19
19
  `;
20
20
  const Files = styled_components.div`
21
- height: 250px;
22
- overflow-y: scroll;
21
+ flex: 1;
22
+ min-height: 0;
23
+ overflow-y: auto;
24
+ overflow-x: hidden;
23
25
  margin: ${spacing.r8} 0px;
24
26
  `;
25
27
  const EmptyFile = styled_components.div`
@@ -38,6 +40,10 @@ const FileRow = styled_components.div`
38
40
  `;
39
41
  const FileInfo = styled_components.div`
40
42
  flex: 1;
43
+ min-width: 0;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
41
47
  `;
42
48
  const RemoveButton = styled_components.button`
43
49
  background: none;
@@ -52,7 +58,12 @@ const RemoveButton = styled_components.button`
52
58
  `;
53
59
  const maybePluralize = (count, word)=>1 === count ? `1 ${word}` : `${count} ${word}s`;
54
60
  const getTitle = (fileCount)=>0 === fileCount ? 'Upload Files' : `Upload ${maybePluralize(fileCount, 'file')}`;
55
- const FileList = ({ acceptedFiles, open, removeFile })=>/*#__PURE__*/ jsxs("div", {
61
+ const FileListWrapper = styled_components.div`
62
+ display: flex;
63
+ flex-direction: column;
64
+ height: 100%;
65
+ `;
66
+ const FileList = ({ acceptedFiles, open, removeFile })=>/*#__PURE__*/ jsxs(FileListWrapper, {
56
67
  children: [
57
68
  /*#__PURE__*/ jsx(Button, {
58
69
  icon: /*#__PURE__*/ jsx(Icon, {
@@ -65,18 +76,17 @@ const FileList = ({ acceptedFiles, open, removeFile })=>/*#__PURE__*/ jsxs("div"
65
76
  /*#__PURE__*/ jsx(Files, {
66
77
  children: acceptedFiles.map((file)=>/*#__PURE__*/ jsxs(FileRow, {
67
78
  children: [
68
- /*#__PURE__*/ jsx(FileInfo, {
69
- children: /*#__PURE__*/ jsxs("div", {
70
- children: [
71
- file.name,
72
- /*#__PURE__*/ jsx("br", {}),
73
- /*#__PURE__*/ jsx("small", {
74
- children: /*#__PURE__*/ jsx(PrettyBytes, {
75
- bytes: file.size
76
- })
79
+ /*#__PURE__*/ jsxs(FileInfo, {
80
+ title: file.name,
81
+ children: [
82
+ file.name,
83
+ /*#__PURE__*/ jsx("br", {}),
84
+ /*#__PURE__*/ jsx("small", {
85
+ children: /*#__PURE__*/ jsx(PrettyBytes, {
86
+ bytes: file.size
77
87
  })
78
- ]
79
- })
88
+ })
89
+ ]
80
90
  }),
81
91
  /*#__PURE__*/ jsx(RemoveButton, {
82
92
  onClick: ()=>removeFile(file.name),
@@ -0,0 +1,96 @@
1
+ import { coreUIAvailableThemes } from "@scality/core-ui/dist/style/theme";
2
+ import { resolveBrandingTheme } from "../resolveBrandingTheme.js";
3
+ describe('resolveBrandingTheme', ()=>{
4
+ it('loads base preset and returns complete theme', ()=>{
5
+ const config = {
6
+ base: 'darkRebrand',
7
+ logo: '/logo.png',
8
+ brandPrimary: '#2563EB',
9
+ brandSecondary: '#1E3A5F'
10
+ };
11
+ const result = resolveBrandingTheme(config);
12
+ expect(result).toHaveProperty('statusHealthy');
13
+ expect(result).toHaveProperty('statusWarning');
14
+ expect(result).toHaveProperty('statusCritical');
15
+ expect(result).toHaveProperty('backgroundLevel1');
16
+ expect(result.backgroundLevel1).toBe(coreUIAvailableThemes.darkRebrand.backgroundLevel1);
17
+ });
18
+ it('applies brandPrimary to selectedActive and buttonPrimary tokens', ()=>{
19
+ const config = {
20
+ base: 'darkRebrand',
21
+ logo: '/logo.png',
22
+ brandPrimary: '#2563EB',
23
+ brandSecondary: '#1E3A5F'
24
+ };
25
+ const result = resolveBrandingTheme(config);
26
+ expect(result.selectedActive).toBe('#2563EB');
27
+ expect(result.buttonPrimary).toBe('#2563EB');
28
+ });
29
+ it('derives highlight color from brandPrimary at 20% opacity', ()=>{
30
+ const config = {
31
+ base: 'darkRebrand',
32
+ logo: '/logo.png',
33
+ brandPrimary: '#2563EB',
34
+ brandSecondary: '#1E3A5F'
35
+ };
36
+ const result = resolveBrandingTheme(config);
37
+ expect(result.highlight).toBe('rgba(37, 99, 235, 0.2)');
38
+ });
39
+ it('applies brandSecondary to navbarBackground', ()=>{
40
+ const config = {
41
+ base: 'darkRebrand',
42
+ logo: '/logo.png',
43
+ brandPrimary: '#2563EB',
44
+ brandSecondary: '#1E3A5F'
45
+ };
46
+ const result = resolveBrandingTheme(config);
47
+ expect(result.navbarBackground).toBe('#1E3A5F');
48
+ });
49
+ it('applies optional overrides on top of generated theme', ()=>{
50
+ const config = {
51
+ base: 'darkRebrand',
52
+ logo: '/logo.png',
53
+ brandPrimary: '#2563EB',
54
+ brandSecondary: '#1E3A5F',
55
+ overrides: {
56
+ textLink: '#71AEFF',
57
+ border: '#333333'
58
+ }
59
+ };
60
+ const result = resolveBrandingTheme(config);
61
+ expect(result.buttonPrimary).toBe('#2563EB');
62
+ expect(result.navbarBackground).toBe('#1E3A5F');
63
+ expect(result.textLink).toBe('#71AEFF');
64
+ expect(result.border).toBe('#333333');
65
+ });
66
+ it('handles 3-character hex shorthand colors', ()=>{
67
+ const config = {
68
+ base: 'darkRebrand',
69
+ logo: '/logo.png',
70
+ brandPrimary: '#FFF',
71
+ brandSecondary: '#000'
72
+ };
73
+ const result = resolveBrandingTheme(config);
74
+ expect(result.buttonPrimary).toBe('#FFF');
75
+ expect(result.highlight).toBe('rgba(255, 255, 255, 0.2)');
76
+ expect(result.navbarBackground).toBe('#000');
77
+ });
78
+ it('throws error when base theme name does not exist', ()=>{
79
+ const config = {
80
+ base: 'foobar',
81
+ logo: '/logo.png',
82
+ brandPrimary: '#2563EB',
83
+ brandSecondary: '#1E3A5F'
84
+ };
85
+ expect(()=>resolveBrandingTheme(config)).toThrow('Unknown base theme "foobar"');
86
+ });
87
+ it('throws error on invalid hex color format', ()=>{
88
+ const config = {
89
+ base: 'darkRebrand',
90
+ logo: '/logo.png',
91
+ brandPrimary: 'not-a-color',
92
+ brandSecondary: '#1E3A5F'
93
+ };
94
+ expect(()=>resolveBrandingTheme(config)).toThrow('Invalid hex color format');
95
+ });
96
+ });
@@ -0,0 +1,16 @@
1
+ import { type CoreUITheme } from '@scality/core-ui/dist/style/theme';
2
+ import type { BrandingModeConfig } from './types';
3
+ /**
4
+ * Resolves a lightweight branding configuration into a complete CoreUITheme.
5
+ *
6
+ * Takes a base preset and brand colors, then generates a full theme by:
7
+ * - Loading all tokens from the specified base preset
8
+ * - Applying brandPrimary to button and active states
9
+ * - Applying brandSecondary to navbar background
10
+ * - Auto-deriving highlight color from brandPrimary
11
+ * - Applying any optional token overrides
12
+ *
13
+ * @param config - Branding configuration with base preset, colors, and optional overrides
14
+ * @returns Complete CoreUITheme ready to use with styled-components ThemeProvider
15
+ */
16
+ export declare function resolveBrandingTheme(config: BrandingModeConfig): CoreUITheme;
@@ -0,0 +1,23 @@
1
+ import { coreUIAvailableThemes } from "@scality/core-ui/dist/style/theme";
2
+ function hexToRgba(hex, opacity) {
3
+ let cleanHex = hex.replace('#', '');
4
+ if (!/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleanHex)) throw new Error(`Invalid hex color format: "${hex}". Expected format: #RGB or #RRGGBB`);
5
+ if (3 === cleanHex.length) cleanHex = cleanHex.split('').map((char)=>char + char).join('');
6
+ const r = Number.parseInt(cleanHex.substring(0, 2), 16);
7
+ const g = Number.parseInt(cleanHex.substring(2, 4), 16);
8
+ const b = Number.parseInt(cleanHex.substring(4, 6), 16);
9
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
10
+ }
11
+ function resolveBrandingTheme(config) {
12
+ const baseTheme = coreUIAvailableThemes[config.base];
13
+ if (!baseTheme) throw new Error(`Unknown base theme "${config.base}". Available themes: ${Object.keys(coreUIAvailableThemes).join(', ')}`);
14
+ return {
15
+ ...baseTheme,
16
+ selectedActive: config.brandPrimary,
17
+ buttonPrimary: config.brandPrimary,
18
+ highlight: hexToRgba(config.brandPrimary, 0.2),
19
+ navbarBackground: config.brandSecondary,
20
+ ...config.overrides
21
+ };
22
+ }
23
+ export { resolveBrandingTheme };
@@ -2,6 +2,7 @@
2
2
  * Runtime configuration types
3
3
  */
4
4
  import type { Bucket } from '@aws-sdk/client-s3';
5
+ import type { CoreUITheme, CoreUIThemeName } from '@scality/core-ui/dist/style/theme';
5
6
  import type { TableItem } from '../components/objects/ObjectList';
6
7
  /**
7
8
  * Proxy configuration for development or custom proxy setups
@@ -264,4 +265,39 @@ export interface DataBrowserUIProps {
264
265
  }
265
266
  export type S3EventType = 's3:ObjectCreated:*' | 's3:ObjectCreated:Put' | 's3:ObjectCreated:Post' | 's3:ObjectCreated:Copy' | 's3:ObjectCreated:CompleteMultipartUpload' | 's3:ObjectRemoved:*' | 's3:ObjectRemoved:Delete' | 's3:ObjectRemoved:DeleteMarkerCreated' | 's3:ObjectRestore:*' | 's3:ObjectRestore:Post' | 's3:ObjectRestore:Completed' | 's3:ObjectRestore:Delete' | 's3:LifecycleExpiration:*' | 's3:LifecycleExpiration:Delete' | 's3:LifecycleExpiration:DeleteMarkerCreated' | 's3:LifecycleTransition' | 's3:Replication:*' | 's3:Replication:OperationFailedReplication' | 's3:Replication:OperationMissedThreshold' | 's3:Replication:OperationReplicatedAfterThreshold' | 's3:Replication:OperationNotTracked' | 's3:ObjectTagging:*' | 's3:ObjectTagging:Put' | 's3:ObjectTagging:Delete' | 's3:ReducedRedundancyLostObject' | 's3:IntelligentTiering' | 's3:ObjectAcl:Put' | 's3:TestEvent';
266
267
  export type S3EventCategory = 'Object Creation' | 'Object Deletion' | 'Object Restoration' | 'Lifecycle' | 'Replication' | 'Object Tagging' | 'Storage & Access' | 'Testing';
268
+ /**
269
+ * Branding configuration for a single theme mode (dark or light).
270
+ *
271
+ * Simplifies theme customization by requiring only a base preset and two brand colors.
272
+ * The branding engine auto-generates a complete theme by:
273
+ * - Loading all tokens from the base preset
274
+ * - Applying brandPrimary to buttons and active states
275
+ * - Applying brandSecondary to navbar background
276
+ * - Auto-deriving hover states and highlights
277
+ * - Allowing optional token overrides for fine-tuning
278
+ */
279
+ export interface BrandingModeConfig {
280
+ /** Base theme preset that provides default values for all tokens */
281
+ base: CoreUIThemeName;
282
+ /** Path to brand logo displayed in navbar. Supports separate logos for dark/light modes. */
283
+ logo: string;
284
+ /** Main brand color applied to buttons, active states, and auto-generates hover color */
285
+ brandPrimary: string;
286
+ /** Brand color applied to navbar background for immediate brand recognition */
287
+ brandSecondary: string;
288
+ /** Optional overrides for individual design tokens when fine-tuning is needed */
289
+ overrides?: Partial<CoreUITheme>;
290
+ }
291
+ /**
292
+ * Complete branding configuration supporting dark and/or light modes.
293
+ *
294
+ * At least one mode must be provided (dark, light, or both).
295
+ */
296
+ export type BrandingConfig = {
297
+ dark: BrandingModeConfig;
298
+ light?: BrandingModeConfig;
299
+ } | {
300
+ dark?: BrandingModeConfig;
301
+ light: BrandingModeConfig;
302
+ };
267
303
  export {};
@@ -1,6 +1,6 @@
1
1
  import { renderHook } from "@testing-library/react";
2
2
  import { createTestWrapper } from "../../test/testUtils.js";
3
- import { BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION } from "../../utils/constants.js";
3
+ import { BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, KASTEN_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION } from "../../utils/constants.js";
4
4
  import { useGetBucketTagging } from "../bucketConfiguration.js";
5
5
  import { useFeatures } from "../useFeatures.js";
6
6
  import { useISVBucketStatus } from "../useISVBucketDetection.js";
@@ -144,6 +144,27 @@ describe('useISVBucketStatus', ()=>{
144
144
  expect(result.current.isISVManaged).toBe(true);
145
145
  expect(result.current.isvApplication).toBe('Commvault');
146
146
  });
147
+ it('should detect Kasten bucket', ()=>{
148
+ mockUseGetBucketTagging.mockReturnValue({
149
+ data: {
150
+ TagSet: [
151
+ {
152
+ Key: BUCKET_TAG_APPLICATION,
153
+ Value: KASTEN_APPLICATION
154
+ }
155
+ ]
156
+ },
157
+ status: 'success'
158
+ });
159
+ const { result } = renderHook(()=>useISVBucketStatus('test-bucket'), {
160
+ wrapper: createTestWrapper()
161
+ });
162
+ expect(result.current.isVeeamBucket).toBe(false);
163
+ expect(result.current.isCommvaultBucket).toBe(false);
164
+ expect(result.current.isKastenBucket).toBe(true);
165
+ expect(result.current.isISVManaged).toBe(true);
166
+ expect(result.current.isvApplication).toBe('Kasten');
167
+ });
147
168
  it('should return false for non-ISV bucket', ()=>{
148
169
  mockUseGetBucketTagging.mockReturnValue({
149
170
  data: {
@@ -8,6 +8,7 @@
8
8
  export declare const useISVBucketStatus: (bucketName: string) => {
9
9
  isVeeamBucket: boolean;
10
10
  isCommvaultBucket: boolean;
11
+ isKastenBucket: boolean;
11
12
  isISVManaged: boolean;
12
13
  isvApplication: string | undefined;
13
14
  isLoading: boolean | undefined;
@@ -1,4 +1,4 @@
1
- import { BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION } from "../utils/constants.js";
1
+ import { BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, KASTEN_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION } from "../utils/constants.js";
2
2
  import { useGetBucketTagging } from "./bucketConfiguration.js";
3
3
  import { useFeatures } from "./useFeatures.js";
4
4
  const useISVBucketStatus = (bucketName)=>{
@@ -13,13 +13,15 @@ const useISVBucketStatus = (bucketName)=>{
13
13
  const isVeeamBucket = veeamTagApplication === VEEAM_BACKUP_REPLICATION || veeamTagApplication === VEEAM_OFFICE_365 || veeamTagApplication === VEEAM_OFFICE_365_V8;
14
14
  const isISVBucketTagAsVeeam = ISVApplicationTag === VEEAM_BACKUP_REPLICATION || ISVApplicationTag === VEEAM_VBO_APPLICATION;
15
15
  const isCommvaultBucket = ISVApplicationTag === COMMVAULT_APPLICATION;
16
+ const isKastenBucket = ISVApplicationTag === KASTEN_APPLICATION;
16
17
  const isVeeam = isVeeamBucket || isISVBucketTagAsVeeam;
17
- const isISVManaged = isVeeam || isCommvaultBucket;
18
+ const isISVManaged = isVeeam || isCommvaultBucket || isKastenBucket;
18
19
  return {
19
20
  isVeeamBucket: isVeeam,
20
21
  isCommvaultBucket,
22
+ isKastenBucket,
21
23
  isISVManaged,
22
- isvApplication: isCommvaultBucket ? 'Commvault' : isVeeam ? 'Veeam' : void 0,
24
+ isvApplication: isKastenBucket ? 'Kasten' : isCommvaultBucket ? 'Commvault' : isVeeam ? 'Veeam' : void 0,
23
25
  isLoading: isISVFeatureEnabled && 'pending' === bucketTagsStatus,
24
26
  bucketTagsStatus
25
27
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './components';
2
2
  export * from './config/factory';
3
+ export * from './config/resolveBrandingTheme';
3
4
  export * from './config/types';
4
5
  export * from './hooks';
5
6
  export * from './types';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./components/index.js";
2
2
  export * from "./config/factory.js";
3
+ export * from "./config/resolveBrandingTheme.js";
3
4
  export * from "./config/types.js";
4
5
  export * from "./hooks/index.js";
5
6
  export * from "./types/index.js";
@@ -158,6 +158,7 @@ export type MockQueryResult<T> = {
158
158
  export type ISVBucketStatusMock = {
159
159
  isVeeamBucket: boolean;
160
160
  isCommvaultBucket: boolean;
161
+ isKastenBucket: boolean;
161
162
  isISVManaged: boolean;
162
163
  isvApplication: string | undefined;
163
164
  isLoading: boolean;
@@ -321,6 +321,7 @@ const createDefaultBucketMocks = ()=>({
321
321
  isvBucketStatus: {
322
322
  isVeeamBucket: false,
323
323
  isCommvaultBucket: false,
324
+ isKastenBucket: false,
324
325
  isISVManaged: false,
325
326
  isvApplication: void 0,
326
327
  isLoading: false,
@@ -10,6 +10,8 @@ export declare const VEEAM_OFFICE_365 = "Veeam Backup for Microsoft 365 (v6, v7)
10
10
  export declare const VEEAM_OFFICE_365_V8 = "Veeam Backup for Microsoft 365 (v8+)";
11
11
  /** Generic identifier for Commvault */
12
12
  export declare const COMMVAULT_APPLICATION = "Commvault";
13
+ /** Generic identifier for Kasten */
14
+ export declare const KASTEN_APPLICATION = "Kasten";
13
15
  export declare const BUCKET_ROUTES: {
14
16
  readonly bucketPolicy: (bucketName: string) => string;
15
17
  readonly bucketCors: (bucketName: string) => string;
@@ -6,6 +6,7 @@ const VEEAM_VBO_APPLICATION = 'Veeam Backup for Microsoft 365';
6
6
  const VEEAM_OFFICE_365 = 'Veeam Backup for Microsoft 365 (v6, v7)';
7
7
  const VEEAM_OFFICE_365_V8 = 'Veeam Backup for Microsoft 365 (v8+)';
8
8
  const COMMVAULT_APPLICATION = 'Commvault';
9
+ const KASTEN_APPLICATION = 'Kasten';
9
10
  const BUCKET_ROUTES = {
10
11
  bucketPolicy: (bucketName)=>`/buckets/${bucketName}/policy`,
11
12
  bucketCors: (bucketName)=>`/buckets/${bucketName}/cors`,
@@ -16,4 +17,4 @@ const BUCKET_ROUTES = {
16
17
  notificationCreate: (bucketName)=>`/buckets/${bucketName}/notifications/create`,
17
18
  notificationEdit: (bucketName, ruleId)=>`/buckets/${bucketName}/notifications/edit/${encodeURIComponent(ruleId)}`
18
19
  };
19
- export { BUCKET_ROUTES, BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_IMMUTABLE_POLICY_NAME, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION };
20
+ export { BUCKET_ROUTES, BUCKET_TAG_APPLICATION, BUCKET_TAG_VEEAM_APPLICATION, COMMVAULT_APPLICATION, KASTEN_APPLICATION, VEEAM_BACKUP_REPLICATION, VEEAM_IMMUTABLE_POLICY_NAME, VEEAM_OFFICE_365, VEEAM_OFFICE_365_V8, VEEAM_VBO_APPLICATION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/data-browser-library",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "A modular React component library for browsing S3 buckets and objects",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "react-hook-form": "^7.48.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@scality/core-ui": ">=0.197.0",
43
+ "@scality/core-ui": ">=0.198.0",
44
44
  "react": ">=18.0.0",
45
45
  "react-dom": ">=18.0.0",
46
46
  "react-router": ">=7.1.3",