@scality/data-browser-library 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/__tests__/BucketCorsPage.test.js +67 -9
- package/dist/components/__tests__/BucketDetails.test.js +1 -0
- package/dist/components/__tests__/BucketLifecycleFormPage.test.js +16 -11
- package/dist/components/__tests__/BucketNotificationFormPage.test.js +45 -0
- package/dist/components/__tests__/BucketOverview.test.js +92 -2
- package/dist/components/__tests__/BucketPolicyPage.test.js +70 -51
- package/dist/components/__tests__/BucketReplicationFormPage.test.js +18 -24
- package/dist/components/__tests__/ObjectList.test.js +43 -2
- package/dist/components/buckets/BucketConfigEditButton.d.ts +2 -0
- package/dist/components/buckets/BucketConfigEditButton.js +9 -3
- package/dist/components/buckets/BucketCorsPage.js +57 -20
- package/dist/components/buckets/BucketDetails.js +27 -2
- package/dist/components/buckets/BucketLifecycleFormPage.js +310 -270
- package/dist/components/buckets/BucketOverview.js +21 -18
- package/dist/components/buckets/BucketPolicyPage.js +119 -83
- package/dist/components/buckets/BucketReplicationFormPage.js +39 -29
- package/dist/components/buckets/BucketVersioning.js +16 -10
- package/dist/components/buckets/__tests__/BucketVersioning.test.js +76 -23
- package/dist/components/buckets/notifications/BucketNotificationFormPage.js +13 -5
- package/dist/components/objects/ObjectList.js +22 -25
- package/dist/components/objects/ObjectLock/EditRetentionButton.js +2 -2
- package/dist/components/objects/UploadButton.js +25 -15
- package/dist/config/__tests__/resolveBrandingTheme.test.d.ts +1 -0
- package/dist/config/__tests__/resolveBrandingTheme.test.js +96 -0
- package/dist/config/resolveBrandingTheme.d.ts +16 -0
- package/dist/config/resolveBrandingTheme.js +23 -0
- package/dist/config/types.d.ts +36 -0
- package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +2 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/objectOperations.d.ts +3 -3
- package/dist/hooks/objectOperations.js +3 -3
- package/dist/hooks/useBucketConfigEditor.d.ts +4 -4
- package/dist/hooks/useBucketConfigEditor.js +16 -31
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/test/mocks/esmOnlyModules.js +4 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/utils/__tests__/proxyMiddleware.test.js +34 -0
- package/dist/utils/proxyMiddleware.js +2 -0
- package/package.json +4 -4
- package/dist/components/Editor.d.ts +0 -12
- package/dist/components/Editor.js +0 -28
- package/dist/types/monaco.d.ts +0 -13
- package/dist/types/monaco.js +0 -0
|
@@ -16,23 +16,22 @@ const mockUseGetBucketVersioning = jest.mocked(useGetBucketVersioning);
|
|
|
16
16
|
const mockUseGetBucketObjectLockConfiguration = jest.mocked(useGetBucketObjectLockConfiguration);
|
|
17
17
|
const mockUseISVBucketStatus = jest.mocked(useISVBucketStatus);
|
|
18
18
|
const mockUseSetBucketVersioning = jest.mocked(useSetBucketVersioning);
|
|
19
|
-
const renderBucketVersioning = (props = {})=>{
|
|
20
|
-
const mockMutate = jest.fn()
|
|
19
|
+
const renderBucketVersioning = (props = {}, { simulateSuccess = false } = {})=>{
|
|
20
|
+
const mockMutate = jest.fn().mockImplementation((_params, options)=>{
|
|
21
|
+
if (simulateSuccess && options?.onSuccess) options.onSuccess();
|
|
22
|
+
});
|
|
21
23
|
mockUseSetBucketVersioning.mockReturnValue(createMockMutationResult(mockMutate));
|
|
22
24
|
const Wrapper = createTestWrapper();
|
|
23
|
-
return {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
...props
|
|
31
|
-
})
|
|
25
|
+
return render(/*#__PURE__*/ jsx(Wrapper, {
|
|
26
|
+
children: /*#__PURE__*/ jsx(BucketOverviewContext.Provider, {
|
|
27
|
+
value: {
|
|
28
|
+
bucketName: 'test-bucket'
|
|
29
|
+
},
|
|
30
|
+
children: /*#__PURE__*/ jsx(BucketVersioning, {
|
|
31
|
+
...props
|
|
32
32
|
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
};
|
|
33
|
+
})
|
|
34
|
+
}));
|
|
36
35
|
};
|
|
37
36
|
const mockHookDefaults = ()=>{
|
|
38
37
|
mockUseGetBucketVersioning.mockReturnValue({
|
|
@@ -86,10 +85,10 @@ describe('BucketVersioning', ()=>{
|
|
|
86
85
|
expect(toggle).not.toBeChecked();
|
|
87
86
|
});
|
|
88
87
|
it('calls mutation with Suspended status when disabling versioning', ()=>{
|
|
89
|
-
|
|
88
|
+
renderBucketVersioning();
|
|
90
89
|
const toggle = findToggleByLabel('Active');
|
|
91
90
|
fireEvent.click(toggle);
|
|
92
|
-
expect(mutate).toHaveBeenCalledWith({
|
|
91
|
+
expect(mockUseSetBucketVersioning().mutate).toHaveBeenCalledWith({
|
|
93
92
|
Bucket: 'test-bucket',
|
|
94
93
|
VersioningConfiguration: {
|
|
95
94
|
Status: 'Suspended'
|
|
@@ -104,10 +103,10 @@ describe('BucketVersioning', ()=>{
|
|
|
104
103
|
status: 'success',
|
|
105
104
|
error: null
|
|
106
105
|
});
|
|
107
|
-
|
|
106
|
+
renderBucketVersioning();
|
|
108
107
|
const toggle = findToggleByLabel('Inactive');
|
|
109
108
|
fireEvent.click(toggle);
|
|
110
|
-
expect(mutate).toHaveBeenCalledWith({
|
|
109
|
+
expect(mockUseSetBucketVersioning().mutate).toHaveBeenCalledWith({
|
|
111
110
|
Bucket: 'test-bucket',
|
|
112
111
|
VersioningConfiguration: {
|
|
113
112
|
Status: 'Enabled'
|
|
@@ -135,16 +134,35 @@ describe('BucketVersioning', ()=>{
|
|
|
135
134
|
});
|
|
136
135
|
renderBucketVersioning();
|
|
137
136
|
expect(screen.getByText('Enabled')).toBeInTheDocument();
|
|
138
|
-
expect(screen.getByText(/Versioning cannot be
|
|
137
|
+
expect(screen.getByText(/Versioning cannot be disabled because Object-lock is enabled/i)).toBeInTheDocument();
|
|
139
138
|
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
|
|
140
139
|
});
|
|
141
|
-
it(
|
|
142
|
-
|
|
140
|
+
it.each([
|
|
141
|
+
{
|
|
142
|
+
isvApplication: 'Veeam',
|
|
143
143
|
isVeeamBucket: true,
|
|
144
144
|
isCommvaultBucket: false,
|
|
145
|
-
isKastenBucket: false
|
|
145
|
+
isKastenBucket: false
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
isvApplication: 'Commvault',
|
|
149
|
+
isVeeamBucket: false,
|
|
150
|
+
isCommvaultBucket: true,
|
|
151
|
+
isKastenBucket: false
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
isvApplication: 'Kasten',
|
|
155
|
+
isVeeamBucket: false,
|
|
156
|
+
isCommvaultBucket: false,
|
|
157
|
+
isKastenBucket: true
|
|
158
|
+
}
|
|
159
|
+
])('disables toggle when bucket is managed by $isvApplication', ({ isvApplication, isVeeamBucket, isCommvaultBucket, isKastenBucket })=>{
|
|
160
|
+
mockUseISVBucketStatus.mockReturnValue({
|
|
161
|
+
isVeeamBucket,
|
|
162
|
+
isCommvaultBucket,
|
|
163
|
+
isKastenBucket,
|
|
146
164
|
isISVManaged: true,
|
|
147
|
-
isvApplication
|
|
165
|
+
isvApplication,
|
|
148
166
|
isLoading: false,
|
|
149
167
|
bucketTagsStatus: 'success'
|
|
150
168
|
});
|
|
@@ -152,6 +170,41 @@ describe('BucketVersioning', ()=>{
|
|
|
152
170
|
const toggle = screen.getByRole('checkbox');
|
|
153
171
|
expect(toggle).toHaveAttribute('aria-disabled', 'true');
|
|
154
172
|
});
|
|
173
|
+
it('shows toast with correct message when disabling versioning', ()=>{
|
|
174
|
+
jest.useFakeTimers();
|
|
175
|
+
renderBucketVersioning({}, {
|
|
176
|
+
simulateSuccess: true
|
|
177
|
+
});
|
|
178
|
+
const toggle = findToggleByLabel('Active');
|
|
179
|
+
fireEvent.click(toggle);
|
|
180
|
+
jest.advanceTimersByTime(500);
|
|
181
|
+
expect(mockShowToast).toHaveBeenCalledWith(expect.objectContaining({
|
|
182
|
+
message: 'Bucket versioning disabled',
|
|
183
|
+
status: 'success'
|
|
184
|
+
}));
|
|
185
|
+
jest.useRealTimers();
|
|
186
|
+
});
|
|
187
|
+
it('shows toast with correct message when enabling versioning', ()=>{
|
|
188
|
+
jest.useFakeTimers();
|
|
189
|
+
mockUseGetBucketVersioning.mockReturnValue({
|
|
190
|
+
data: {
|
|
191
|
+
Status: 'Suspended'
|
|
192
|
+
},
|
|
193
|
+
status: 'success',
|
|
194
|
+
error: null
|
|
195
|
+
});
|
|
196
|
+
renderBucketVersioning({}, {
|
|
197
|
+
simulateSuccess: true
|
|
198
|
+
});
|
|
199
|
+
const toggle = findToggleByLabel('Inactive');
|
|
200
|
+
fireEvent.click(toggle);
|
|
201
|
+
jest.advanceTimersByTime(500);
|
|
202
|
+
expect(mockShowToast).toHaveBeenCalledWith(expect.objectContaining({
|
|
203
|
+
message: 'Bucket versioning enabled',
|
|
204
|
+
status: 'success'
|
|
205
|
+
}));
|
|
206
|
+
jest.useRealTimers();
|
|
207
|
+
});
|
|
155
208
|
it('disables toggle when versioning query errors', ()=>{
|
|
156
209
|
mockUseGetBucketVersioning.mockReturnValue({
|
|
157
210
|
data: void 0,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { joiResolver } from "@hookform/resolvers/joi";
|
|
3
3
|
import { Form, FormGroup, FormSection, Icon, Loader, Stack, Text, spacing, useToast } from "@scality/core-ui";
|
|
4
4
|
import { convertRemToPixels } from "@scality/core-ui/dist/components/tablev2/TableUtils";
|
|
@@ -9,6 +9,7 @@ import { FormProvider, useForm } from "react-hook-form";
|
|
|
9
9
|
import { useParams } from "react-router";
|
|
10
10
|
import { useGetBucketNotification, useSetBucketNotification } from "../../../hooks/index.js";
|
|
11
11
|
import { useDataBrowserNavigate } from "../../../hooks/useDataBrowserNavigate.js";
|
|
12
|
+
import { useSupportedNotificationEvents } from "../../../hooks/useSupportedNotificationEvents.js";
|
|
12
13
|
import { EventsSection } from "./EventsSection.js";
|
|
13
14
|
const baseSchema = joi.object({
|
|
14
15
|
ruleName: joi.string().required().messages({
|
|
@@ -29,6 +30,7 @@ function BucketNotificationFormPage() {
|
|
|
29
30
|
const navigate = useDataBrowserNavigate();
|
|
30
31
|
const { showToast } = useToast();
|
|
31
32
|
const isEditMode = !!ruleId;
|
|
33
|
+
const supportedEvents = useSupportedNotificationEvents();
|
|
32
34
|
const { data: existingNotificationData, status: notificationStatus, error: notificationError } = useGetBucketNotification({
|
|
33
35
|
Bucket: bucketName
|
|
34
36
|
});
|
|
@@ -70,10 +72,15 @@ function BucketNotificationFormPage() {
|
|
|
70
72
|
if (isEditMode && existingRule) {
|
|
71
73
|
const prefixRule = existingRule.Filter?.Key?.FilterRules?.find((r)=>'prefix' === r.Name);
|
|
72
74
|
const suffixRule = existingRule.Filter?.Key?.FilterRules?.find((r)=>'suffix' === r.Name);
|
|
75
|
+
let events = existingRule.Events || [];
|
|
76
|
+
if (supportedEvents?.length) {
|
|
77
|
+
const supported = new Set(supportedEvents);
|
|
78
|
+
events = events.filter((event)=>supported.has(event));
|
|
79
|
+
}
|
|
73
80
|
reset({
|
|
74
81
|
ruleName: existingRule.Id || '',
|
|
75
82
|
queueArn: existingRule.QueueArn || '',
|
|
76
|
-
events
|
|
83
|
+
events,
|
|
77
84
|
prefix: prefixRule?.Value || '',
|
|
78
85
|
suffix: suffixRule?.Value || ''
|
|
79
86
|
});
|
|
@@ -81,7 +88,8 @@ function BucketNotificationFormPage() {
|
|
|
81
88
|
}, [
|
|
82
89
|
isEditMode,
|
|
83
90
|
existingRule,
|
|
84
|
-
reset
|
|
91
|
+
reset,
|
|
92
|
+
supportedEvents
|
|
85
93
|
]);
|
|
86
94
|
const handleCancel = useCallback(()=>{
|
|
87
95
|
navigate(`/buckets/${bucketName}?tab=notification`);
|
|
@@ -248,7 +256,7 @@ function BucketNotificationFormPage() {
|
|
|
248
256
|
direction: "horizontal",
|
|
249
257
|
error: errors?.ruleName?.message,
|
|
250
258
|
helpErrorPosition: "bottom",
|
|
251
|
-
labelHelpTooltip:
|
|
259
|
+
labelHelpTooltip: "Must be unique across all notification rules in this bucket. Cannot be changed after creation.",
|
|
252
260
|
required: true,
|
|
253
261
|
content: isEditMode ? /*#__PURE__*/ jsx(Text, {
|
|
254
262
|
children: existingRule?.Id
|
|
@@ -267,7 +275,7 @@ function BucketNotificationFormPage() {
|
|
|
267
275
|
direction: "horizontal",
|
|
268
276
|
error: errors?.queueArn?.message,
|
|
269
277
|
helpErrorPosition: "bottom",
|
|
270
|
-
labelHelpTooltip:
|
|
278
|
+
labelHelpTooltip: "Expected format: arn:aws:sqs:region:account-id:queue-name",
|
|
271
279
|
required: true,
|
|
272
280
|
content: /*#__PURE__*/ jsx(Input, {
|
|
273
281
|
id: "queueArn",
|
|
@@ -5,7 +5,7 @@ import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "rea
|
|
|
5
5
|
import { useLocation, useNavigate } from "react-router";
|
|
6
6
|
import styled_components from "styled-components";
|
|
7
7
|
import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
|
|
8
|
-
import { useSearchObjects, useSearchObjectsVersions } from "../../hooks/index.js";
|
|
8
|
+
import { useListObjectVersions, useListObjects, useSearchObjects, useSearchObjectsVersions } from "../../hooks/index.js";
|
|
9
9
|
import { useGetPresignedDownload } from "../../hooks/presignedOperations.js";
|
|
10
10
|
import { useBatchObjectLegalHold } from "../../hooks/useBatchObjectLegalHold.js";
|
|
11
11
|
import { useFeatures } from "../../hooks/useFeatures.js";
|
|
@@ -370,28 +370,8 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSele
|
|
|
370
370
|
getPresignedDownload,
|
|
371
371
|
showToast
|
|
372
372
|
]);
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
Bucket: bucketName,
|
|
376
|
-
Prefix: prefix,
|
|
377
|
-
MaxKeys: DEFAULT_PAGE_SIZE,
|
|
378
|
-
Delimiter: '/'
|
|
379
|
-
};
|
|
380
|
-
if (isMetadataSearchEnabled && metadataSearchQuery) return {
|
|
381
|
-
...baseParams,
|
|
382
|
-
Search: metadataSearchQuery
|
|
383
|
-
};
|
|
384
|
-
return baseParams;
|
|
385
|
-
}, [
|
|
386
|
-
bucketName,
|
|
387
|
-
prefix,
|
|
388
|
-
isMetadataSearchEnabled,
|
|
389
|
-
metadataSearchQuery
|
|
390
|
-
]);
|
|
391
|
-
const listObjectsQuery = useSearchObjects(searchParams, {
|
|
392
|
-
enabled: Boolean(bucketName)
|
|
393
|
-
});
|
|
394
|
-
const versionSearchParams = useMemo(()=>({
|
|
373
|
+
const isSearchActive = isMetadataSearchEnabled && Boolean(metadataSearchQuery);
|
|
374
|
+
const baseParams = useMemo(()=>({
|
|
395
375
|
Bucket: bucketName,
|
|
396
376
|
Prefix: prefix,
|
|
397
377
|
MaxKeys: DEFAULT_PAGE_SIZE,
|
|
@@ -400,9 +380,26 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSele
|
|
|
400
380
|
bucketName,
|
|
401
381
|
prefix
|
|
402
382
|
]);
|
|
403
|
-
const
|
|
404
|
-
enabled:
|
|
383
|
+
const regularListQuery = useListObjects(baseParams, {
|
|
384
|
+
enabled: Boolean(bucketName) && !isSearchActive
|
|
385
|
+
});
|
|
386
|
+
const searchListQuery = useSearchObjects({
|
|
387
|
+
...baseParams,
|
|
388
|
+
Query: metadataSearchQuery || ''
|
|
389
|
+
}, {
|
|
390
|
+
enabled: Boolean(bucketName) && isSearchActive
|
|
391
|
+
});
|
|
392
|
+
const listObjectsQuery = isSearchActive ? searchListQuery : regularListQuery;
|
|
393
|
+
const regularVersionsQuery = useListObjectVersions(baseParams, {
|
|
394
|
+
enabled: showVersions && Boolean(bucketName) && !isSearchActive
|
|
395
|
+
});
|
|
396
|
+
const searchVersionsQuery = useSearchObjectsVersions({
|
|
397
|
+
...baseParams,
|
|
398
|
+
Query: metadataSearchQuery || ''
|
|
399
|
+
}, {
|
|
400
|
+
enabled: showVersions && Boolean(bucketName) && isSearchActive
|
|
405
401
|
});
|
|
402
|
+
const listVersionsQuery = isSearchActive ? searchVersionsQuery : regularVersionsQuery;
|
|
406
403
|
useEffect(()=>{
|
|
407
404
|
setSelectedObjects([]);
|
|
408
405
|
onObjectSelectRef.current(null);
|
|
@@ -5,8 +5,8 @@ import { useISVBucketStatus } from "../../../hooks/index.js";
|
|
|
5
5
|
import { useDataBrowserNavigate } from "../../../hooks/useDataBrowserNavigate.js";
|
|
6
6
|
const EditRetentionButton = ({ bucketName })=>{
|
|
7
7
|
const navigate = useDataBrowserNavigate();
|
|
8
|
-
const {
|
|
9
|
-
const tooltipOverlay =
|
|
8
|
+
const { isISVManaged, isvApplication, isLoading } = useISVBucketStatus(bucketName);
|
|
9
|
+
const tooltipOverlay = isISVManaged ? `Edition is disabled as it is managed by ${isvApplication}.` : void 0;
|
|
10
10
|
return /*#__PURE__*/ jsx(Button, {
|
|
11
11
|
id: "edit-retention-btn",
|
|
12
12
|
variant: "outline",
|
|
@@ -9,7 +9,7 @@ const DropZone = styled_components.div`
|
|
|
9
9
|
flex: 1;
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-direction: column;
|
|
12
|
-
height:
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
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__*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 };
|
package/dist/config/types.d.ts
CHANGED
|
@@ -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 {};
|
|
@@ -46,6 +46,7 @@ function getPaginationParams(operationName, pageParam) {
|
|
|
46
46
|
ContinuationToken: pageParam
|
|
47
47
|
};
|
|
48
48
|
case 'ListObjectVersions':
|
|
49
|
+
case 'SearchObjectsVersions':
|
|
49
50
|
if (pageParam.includes('|')) {
|
|
50
51
|
const [keyMarker, versionIdMarker] = pageParam.split('|');
|
|
51
52
|
return {
|
|
@@ -71,6 +72,7 @@ function getNextPageToken(operationName, lastPage) {
|
|
|
71
72
|
case 'ListObjects':
|
|
72
73
|
return lastPage.IsTruncated ? lastPage.NextContinuationToken : void 0;
|
|
73
74
|
case 'ListObjectVersions':
|
|
75
|
+
case 'SearchObjectsVersions':
|
|
74
76
|
return lastPage.IsTruncated && (lastPage.NextKeyMarker || lastPage.NextVersionIdMarker) ? `${lastPage.NextKeyMarker || ''}|${lastPage.NextVersionIdMarker || ''}` : void 0;
|
|
75
77
|
default:
|
|
76
78
|
return lastPage.IsTruncated ? lastPage.NextContinuationToken : void 0;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { useCopyObject, useCreateFolder, useDeleteObject, useDeleteObjects, useD
|
|
|
6
6
|
export { useGetPresignedDownload, useGetPresignedPost, useGetPresignedUpload, } from './presignedOperations';
|
|
7
7
|
export { getAccessibleBucketsStorageKey, getLimitedAccessFlagKey, setLimitedAccessFlag, useAccessibleBuckets, } from './useAccessibleBuckets';
|
|
8
8
|
export { useBatchObjectLegalHold } from './useBatchObjectLegalHold';
|
|
9
|
-
export { type BucketConfigEditorConfig, type BucketConfigEditorResult, useBucketConfigEditor
|
|
9
|
+
export { type BucketConfigEditorConfig, type BucketConfigEditorResult, useBucketConfigEditor } from './useBucketConfigEditor';
|
|
10
10
|
export { useBucketLocations } from './useBucketLocations';
|
|
11
11
|
export { useDeleteBucketConfigRule } from './useDeleteBucketConfigRule';
|
|
12
12
|
export { useEmptyBucket } from './useEmptyBucket';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* configurations like retention and legal hold.
|
|
7
7
|
*/
|
|
8
8
|
import { type CopyObjectCommandInput, type CopyObjectCommandOutput, type DeleteObjectCommandInput, type DeleteObjectCommandOutput, type DeleteObjectsCommandInput, type DeleteObjectsCommandOutput, type DeleteObjectTaggingCommandInput, type DeleteObjectTaggingCommandOutput, type GetObjectAclCommandInput, type GetObjectAclCommandOutput, type GetObjectAttributesCommandInput, type GetObjectAttributesCommandOutput, type GetObjectCommandInput, type GetObjectCommandOutput, type GetObjectLegalHoldCommandInput, type GetObjectLegalHoldCommandOutput, type GetObjectRetentionCommandInput, type GetObjectRetentionCommandOutput, type GetObjectTaggingCommandInput, type GetObjectTaggingCommandOutput, type GetObjectTorrentCommandInput, type GetObjectTorrentCommandOutput, type HeadObjectCommandInput, type HeadObjectCommandOutput, type ListMultipartUploadsCommandInput, type ListMultipartUploadsCommandOutput, type ListObjectsV2CommandInput, type ListObjectVersionsCommandInput, type PutObjectAclCommandInput, type PutObjectAclCommandOutput, type PutObjectCommandInput, type PutObjectCommandOutput, type PutObjectLegalHoldCommandInput, type PutObjectLegalHoldCommandOutput, type PutObjectRetentionCommandInput, type PutObjectRetentionCommandOutput, type PutObjectTaggingCommandInput, type PutObjectTaggingCommandOutput, type RestoreObjectCommandInput, type RestoreObjectCommandOutput, type SelectObjectContentCommandInput, type SelectObjectContentCommandOutput } from '@aws-sdk/client-s3';
|
|
9
|
-
import { type
|
|
9
|
+
import { type ListObjectsV2ExtendedInput, type ListObjectVersionsExtendedInput } from '@scality/cloudserverclient';
|
|
10
10
|
/**
|
|
11
11
|
* Hook for listing S3 objects with infinite scroll support
|
|
12
12
|
*
|
|
@@ -27,14 +27,14 @@ export declare const useListObjectVersions: (params: ListObjectVersionsCommandIn
|
|
|
27
27
|
* Provides paginated search of objects in an S3 bucket using SQL-like query syntax.
|
|
28
28
|
* Supports advanced filtering and pattern matching on object keys and metadata.
|
|
29
29
|
*/
|
|
30
|
-
export declare const useSearchObjects: (params:
|
|
30
|
+
export declare const useSearchObjects: (params: ListObjectsV2ExtendedInput, options?: Partial<import("@tanstack/react-query").UseInfiniteQueryOptions<any, import("..").EnhancedS3Error, any, (string | ListObjectsV2ExtendedInput)[], string | undefined>> | undefined) => import("@tanstack/react-query").UseInfiniteQueryResult<any, import("..").EnhancedS3Error>;
|
|
31
31
|
/**
|
|
32
32
|
* Hook for searching S3 object versions with infinite scroll support
|
|
33
33
|
*
|
|
34
34
|
* Provides paginated search of object versions in an S3 bucket using SQL-like query syntax.
|
|
35
35
|
* Supports advanced filtering and pattern matching on object keys and metadata across all versions.
|
|
36
36
|
*/
|
|
37
|
-
export declare const useSearchObjectsVersions: (params:
|
|
37
|
+
export declare const useSearchObjectsVersions: (params: ListObjectVersionsExtendedInput, options?: Partial<import("@tanstack/react-query").UseInfiniteQueryOptions<any, import("..").EnhancedS3Error, any, (string | ListObjectVersionsExtendedInput)[], string | undefined>> | undefined) => import("@tanstack/react-query").UseInfiniteQueryResult<any, import("..").EnhancedS3Error>;
|
|
38
38
|
/**
|
|
39
39
|
* Hook for retrieving S3 object metadata
|
|
40
40
|
*
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CopyObjectCommand, DeleteObjectCommand, DeleteObjectTaggingCommand, DeleteObjectsCommand, GetObjectAclCommand, GetObjectAttributesCommand, GetObjectCommand, GetObjectLegalHoldCommand, GetObjectRetentionCommand, GetObjectTaggingCommand, GetObjectTorrentCommand, HeadObjectCommand, ListMultipartUploadsCommand, ListObjectVersionsCommand, ListObjectsV2Command, PutObjectAclCommand, PutObjectCommand, PutObjectLegalHoldCommand, PutObjectRetentionCommand, PutObjectTaggingCommand, RestoreObjectCommand, SelectObjectContentCommand } from "@aws-sdk/client-s3";
|
|
2
|
-
import {
|
|
2
|
+
import { ListObjectVersionsExtendedCommand, ListObjectsV2ExtendedCommand } from "@scality/cloudserverclient";
|
|
3
3
|
import { useCreateS3InfiniteQueryHook } from "./factories/useCreateS3InfiniteQueryHook.js";
|
|
4
4
|
import { useCreateS3MutationHook } from "./factories/useCreateS3MutationHook.js";
|
|
5
5
|
import { useCreateS3QueryHook } from "./factories/useCreateS3QueryHook.js";
|
|
6
6
|
const useListObjects = useCreateS3InfiniteQueryHook(ListObjectsV2Command, 'ListObjects');
|
|
7
7
|
const useListObjectVersions = useCreateS3InfiniteQueryHook(ListObjectVersionsCommand, 'ListObjectVersions');
|
|
8
|
-
const useSearchObjects = useCreateS3InfiniteQueryHook(
|
|
9
|
-
const useSearchObjectsVersions = useCreateS3InfiniteQueryHook(
|
|
8
|
+
const useSearchObjects = useCreateS3InfiniteQueryHook(ListObjectsV2ExtendedCommand, 'SearchObjects');
|
|
9
|
+
const useSearchObjectsVersions = useCreateS3InfiniteQueryHook(ListObjectVersionsExtendedCommand, 'SearchObjectsVersions');
|
|
10
10
|
const useObjectMetadata = useCreateS3QueryHook(HeadObjectCommand, 'HeadObject');
|
|
11
11
|
const useGetObject = useCreateS3QueryHook(GetObjectCommand, 'GetObject');
|
|
12
12
|
const usePutObject = useCreateS3MutationHook(PutObjectCommand, 'PutObject', [
|