@scality/data-browser-library 1.0.8 → 1.1.0

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 +67 -9
  2. package/dist/components/__tests__/BucketDetails.test.js +1 -0
  3. package/dist/components/__tests__/BucketLifecycleFormPage.test.js +16 -11
  4. package/dist/components/__tests__/BucketNotificationFormPage.test.js +45 -0
  5. package/dist/components/__tests__/BucketOverview.test.js +92 -2
  6. package/dist/components/__tests__/BucketPolicyPage.test.js +70 -51
  7. package/dist/components/__tests__/BucketReplicationFormPage.test.js +51 -24
  8. package/dist/components/__tests__/ObjectList.test.js +43 -2
  9. package/dist/components/buckets/BucketConfigEditButton.d.ts +2 -0
  10. package/dist/components/buckets/BucketConfigEditButton.js +9 -3
  11. package/dist/components/buckets/BucketCorsPage.js +57 -20
  12. package/dist/components/buckets/BucketDetails.js +27 -2
  13. package/dist/components/buckets/BucketLifecycleFormPage.js +310 -270
  14. package/dist/components/buckets/BucketOverview.js +21 -18
  15. package/dist/components/buckets/BucketPolicyPage.js +119 -83
  16. package/dist/components/buckets/BucketReplicationFormPage.d.ts +1 -0
  17. package/dist/components/buckets/BucketReplicationFormPage.js +165 -129
  18. package/dist/components/buckets/BucketVersioning.js +16 -10
  19. package/dist/components/buckets/__tests__/BucketVersioning.test.js +76 -23
  20. package/dist/components/buckets/notifications/BucketNotificationFormPage.js +13 -5
  21. package/dist/components/index.d.ts +1 -1
  22. package/dist/components/index.js +2 -2
  23. package/dist/components/objects/ObjectDetails/__tests__/ObjectDetails.test.js +3 -3
  24. package/dist/components/objects/ObjectList.js +22 -25
  25. package/dist/components/objects/ObjectLock/EditRetentionButton.js +2 -2
  26. package/dist/config/types.d.ts +11 -0
  27. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +2 -0
  28. package/dist/hooks/index.d.ts +1 -1
  29. package/dist/hooks/objectOperations.d.ts +3 -3
  30. package/dist/hooks/objectOperations.js +3 -3
  31. package/dist/hooks/useBucketConfigEditor.d.ts +4 -4
  32. package/dist/hooks/useBucketConfigEditor.js +16 -31
  33. package/dist/test/mocks/esmOnlyModules.js +4 -0
  34. package/dist/types/index.d.ts +0 -1
  35. package/dist/utils/__tests__/proxyMiddleware.test.js +34 -0
  36. package/dist/utils/proxyMiddleware.js +2 -0
  37. package/package.json +4 -4
  38. package/dist/components/Editor.d.ts +0 -12
  39. package/dist/components/Editor.js +0 -28
  40. package/dist/types/monaco.d.ts +0 -13
  41. package/dist/types/monaco.js +0 -0
@@ -3,14 +3,13 @@ import { ConstrainedText, Icon, Loader, Stack, spacing } from "@scality/core-ui"
3
3
  import { Box, Button } from "@scality/core-ui/dist/next";
4
4
  import { createContext, memo, useContext, useMemo } from "react";
5
5
  import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
6
- import { useGetBucketAcl, useGetBucketCors, useGetBucketObjectLockConfiguration, useGetBucketPolicy, useGetBucketVersioning } from "../../hooks/index.js";
6
+ import { useGetBucketAcl, useGetBucketCors, useGetBucketObjectLockConfiguration, useGetBucketPolicy, useGetBucketVersioning, useISVBucketStatus } from "../../hooks/index.js";
7
7
  import { isNotFoundError } from "../../utils/errorHandling.js";
8
8
  import { EditRetentionButton } from "../objects/ObjectLock/EditRetentionButton.js";
9
9
  import { useDataBrowserConfig } from "../providers/DataBrowserProvider.js";
10
10
  import { Body, Group, GroupContent, GroupName, GroupValues, Key, Row, Table, TableContainer, Value } from "../ui/Table.elements.js";
11
11
  import { BucketConfigEditButton } from "./BucketConfigEditButton.js";
12
12
  import { BucketLocation } from "./BucketLocation.js";
13
- const ERROR_NO_SUCH_BUCKET_POLICY = 'NoSuchBucketPolicy';
14
13
  const DEFAULT_PUBLIC_ACL_URI = 'http://acs.amazonaws.com/groups/global/AllUsers';
15
14
  const STATUS_PENDING = 'pending';
16
15
  const STATUS_ERROR = 'error';
@@ -155,12 +154,10 @@ const createAclField = (bucketName, aclData, aclStatus, aclField, renderAcl)=>{
155
154
  })
156
155
  };
157
156
  };
158
- const createCorsField = (bucketName, corsData, corsStatus, corsError, onEditCors, corsField, renderCors)=>{
157
+ const createCorsField = (bucketName, corsData, corsStatus, corsError, onEditCors, corsField, renderCors, isISVManaged, isvApplication)=>{
159
158
  let defaultValue;
160
- const ERROR_NO_SUCH_CORS = 'NoSuchCORSConfiguration';
161
- if (corsStatus === STATUS_ERROR && corsError?.name !== ERROR_NO_SUCH_CORS) defaultValue = /*#__PURE__*/ jsx(ErrorField, {});
162
- else {
163
- const hasCors = !!(corsData?.CORSRules && corsData.CORSRules.length > 0);
159
+ if (corsStatus !== STATUS_ERROR || isNotFoundError(corsError)) {
160
+ const hasCors = !isNotFoundError(corsError) && !!(corsData?.CORSRules && corsData.CORSRules.length > 0);
164
161
  const handleEditCors = ()=>onEditCors?.(bucketName);
165
162
  defaultValue = /*#__PURE__*/ jsxs(Box, {
166
163
  display: "flex",
@@ -175,11 +172,13 @@ const createCorsField = (bucketName, corsData, corsStatus, corsError, onEditCors
175
172
  hasConfig: hasCors,
176
173
  isLoading: corsStatus === STATUS_PENDING,
177
174
  onEdit: handleEditCors,
178
- configName: "bucket CORS"
175
+ configName: "bucket CORS",
176
+ disabled: isISVManaged,
177
+ tooltipOverlay: isISVManaged ? `Edition is disabled as it is managed by ${isvApplication}.` : void 0
179
178
  })
180
179
  ]
181
180
  });
182
- }
181
+ } else defaultValue = /*#__PURE__*/ jsx(ErrorField, {});
183
182
  return {
184
183
  id: 'cors',
185
184
  label: 'CORS',
@@ -202,11 +201,10 @@ const createPublicField = (bucketName, aclStatus, isPublic, publicField, renderP
202
201
  })
203
202
  };
204
203
  };
205
- const createBucketPolicyField = (bucketName, policyData, policyStatus, policyError, onEditPolicy, bucketPolicyField, renderBucketPolicy)=>{
204
+ const createBucketPolicyField = (bucketName, policyData, policyStatus, policyError, onEditPolicy, bucketPolicyField, renderBucketPolicy, isISVManaged, isvApplication)=>{
206
205
  let defaultValue;
207
- if (policyStatus === STATUS_ERROR && policyError?.name !== ERROR_NO_SUCH_BUCKET_POLICY) defaultValue = /*#__PURE__*/ jsx(ErrorField, {});
208
- else {
209
- const hasPolicy = !!policyData?.Policy;
206
+ if (policyStatus !== STATUS_ERROR || isNotFoundError(policyError)) {
207
+ const hasPolicy = !isNotFoundError(policyError) && !!policyData?.Policy;
210
208
  const handleEditPolicy = ()=>onEditPolicy?.(bucketName);
211
209
  defaultValue = /*#__PURE__*/ jsxs(Box, {
212
210
  display: "flex",
@@ -221,11 +219,13 @@ const createBucketPolicyField = (bucketName, policyData, policyStatus, policyErr
221
219
  hasConfig: hasPolicy,
222
220
  isLoading: policyStatus === STATUS_PENDING,
223
221
  onEdit: handleEditPolicy,
224
- configName: "bucket policy"
222
+ configName: "bucket policy",
223
+ disabled: isISVManaged,
224
+ tooltipOverlay: isISVManaged ? `Edition is disabled as it is managed by ${isvApplication}.` : void 0
225
225
  })
226
226
  ]
227
227
  });
228
- }
228
+ } else defaultValue = /*#__PURE__*/ jsx(ErrorField, {});
229
229
  return {
230
230
  id: 'bucketPolicy',
231
231
  label: 'Bucket Policy',
@@ -424,6 +424,7 @@ const PermissionsSection = /*#__PURE__*/ memo(({ onEditPolicy, onEditCors, owner
424
424
  const { bucketName } = useBucketOverviewContext();
425
425
  const { extraBucketOverviewPermissions } = useDataBrowserUICustomization();
426
426
  const config = useDataBrowserConfig();
427
+ const { isISVManaged, isvApplication } = useISVBucketStatus(bucketName);
427
428
  const { data: aclData, status: aclStatus } = useGetBucketAcl({
428
429
  Bucket: bucketName
429
430
  });
@@ -441,9 +442,9 @@ const PermissionsSection = /*#__PURE__*/ memo(({ onEditPolicy, onEditCors, owner
441
442
  const defaultFields = {
442
443
  owner: createOwnerField(bucketName, aclData, aclStatus, ownerField, renderOwner),
443
444
  acl: createAclField(bucketName, aclData, aclStatus, aclField, renderAcl),
444
- cors: createCorsField(bucketName, corsData, corsStatus, corsError, onEditCors, corsField, renderCors),
445
+ cors: createCorsField(bucketName, corsData, corsStatus, corsError, onEditCors, corsField, renderCors, isISVManaged, isvApplication),
445
446
  public: createPublicField(bucketName, aclStatus, isPublic, publicField, renderPublic),
446
- bucketPolicy: createBucketPolicyField(bucketName, policyData, policyStatus, policyError, onEditPolicy, bucketPolicyField, renderBucketPolicy)
447
+ bucketPolicy: createBucketPolicyField(bucketName, policyData, policyStatus, policyError, onEditPolicy, bucketPolicyField, renderBucketPolicy, isISVManaged, isvApplication)
447
448
  };
448
449
  return mergeFieldsWithExtras(defaultFields, extraBucketOverviewPermissions, bucketName, [
449
450
  'owner',
@@ -475,7 +476,9 @@ const PermissionsSection = /*#__PURE__*/ memo(({ onEditPolicy, onEditCors, owner
475
476
  policyData,
476
477
  policyError,
477
478
  policyStatus,
478
- isPublic
479
+ isPublic,
480
+ isISVManaged,
481
+ isvApplication
479
482
  ]);
480
483
  return /*#__PURE__*/ jsx(Section, {
481
484
  title: "Permissions",
@@ -1,12 +1,13 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Banner, Form, FormGroup, FormSection, Icon, Loader, Stack, Text, useToast } from "@scality/core-ui";
3
- import { Box, Button, CopyButton } from "@scality/core-ui/dist/next";
4
- import { useCallback, useMemo } from "react";
2
+ import { Form, FormGroup, FormSection, Icon, Loader, Stack, Text, useToast } from "@scality/core-ui";
3
+ import { Box, Button, CopyButton, Editor } from "@scality/core-ui/dist/next";
4
+ import { useCallback, useEffect, useMemo } from "react";
5
5
  import { Controller } from "react-hook-form";
6
6
  import { useParams } from "react-router";
7
- import { useBucketConfigEditor, useGetBucketPolicy, useISVBucketStatus, useSetBucketPolicy } from "../../hooks/index.js";
7
+ import { useBucketConfigEditor, useDeleteBucketPolicy, useGetBucketPolicy, useISVBucketStatus, useSetBucketPolicy } from "../../hooks/index.js";
8
+ import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
8
9
  import bucketPolicySchema from "../../schemas/bucketPolicySchema.json";
9
- import { Editor } from "../Editor.js";
10
+ import { isNotFoundError } from "../../utils/errorHandling.js";
10
11
  const getDefaultPolicyTemplate = (bucketName)=>`{
11
12
  "Version": "2012-10-17",
12
13
  "Statement": [
@@ -20,8 +21,12 @@ const getDefaultPolicyTemplate = (bucketName)=>`{
20
21
  ]
21
22
  }`;
22
23
  const validatePolicyConfig = (content)=>{
24
+ const trimmed = content.trim();
25
+ if ('' === trimmed) return {
26
+ isValid: true
27
+ };
23
28
  try {
24
- JSON.parse(content);
29
+ JSON.parse(trimmed);
25
30
  return {
26
31
  isValid: true
27
32
  };
@@ -35,33 +40,65 @@ const validatePolicyConfig = (content)=>{
35
40
  const BucketPolicyPage = ()=>{
36
41
  const { bucketName } = useParams();
37
42
  const { showToast } = useToast();
38
- const { isISVManaged, isvApplication, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
43
+ const { isISVManaged, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
44
+ const navigate = useDataBrowserNavigate();
45
+ useEffect(()=>{
46
+ if (isISVManaged) navigate(`/buckets/${bucketName}`, {
47
+ replace: true
48
+ });
49
+ }, [
50
+ isISVManaged,
51
+ bucketName,
52
+ navigate
53
+ ]);
54
+ const shouldFetchData = !isISVManaged && !isISVLoading;
39
55
  const { data: policyData, status: policyStatus, error: policyError } = useGetBucketPolicy({
40
56
  Bucket: bucketName
57
+ }, {
58
+ enabled: shouldFetchData
41
59
  });
42
60
  const initialContent = useMemo(()=>{
61
+ if (isNotFoundError(policyError)) return;
43
62
  if (policyData?.Policy) return policyData.Policy;
44
63
  }, [
45
- policyData
64
+ policyData,
65
+ policyError
46
66
  ]);
47
- const { form, content, isCreateMode, isValidFormat, handleBeforeMount, navigateToBucket } = useBucketConfigEditor({
67
+ const { form, content, isCreateMode, isValidFormat, jsonSchema, navigateToBucket, loadTemplate } = useBucketConfigEditor({
48
68
  bucketName: bucketName,
49
69
  initialContent,
50
70
  defaultTemplate: getDefaultPolicyTemplate(bucketName),
51
71
  isLoading: 'pending' === policyStatus,
52
- notFoundErrorNames: [
53
- 'NoSuchBucketPolicy'
54
- ],
55
72
  errorInstance: policyError,
56
73
  validate: validatePolicyConfig,
57
- monacoSchema: bucketPolicySchema
74
+ jsonSchema: bucketPolicySchema
58
75
  });
59
76
  const { control, handleSubmit, setError, clearErrors, formState: { isDirty, errors } } = form;
60
77
  const { mutate: savePolicy, isPending: isSaving } = useSetBucketPolicy();
78
+ const { mutate: deletePolicy, isPending: isDeleting } = useDeleteBucketPolicy();
61
79
  const onSubmit = useCallback((data)=>{
62
80
  const result = validatePolicyConfig(data.content);
63
81
  if (!result.isValid) return;
64
- const minifiedPolicy = JSON.stringify(JSON.parse(data.content));
82
+ const trimmed = data.content.trim();
83
+ if ('' === trimmed) return void deletePolicy({
84
+ Bucket: bucketName
85
+ }, {
86
+ onSuccess: ()=>{
87
+ showToast({
88
+ open: true,
89
+ message: 'Bucket policy deleted',
90
+ status: 'success'
91
+ });
92
+ navigateToBucket();
93
+ },
94
+ onError: (error)=>{
95
+ setError('content', {
96
+ type: 'server',
97
+ message: error instanceof Error ? error.message : 'Failed to delete'
98
+ });
99
+ }
100
+ });
101
+ const minifiedPolicy = JSON.stringify(JSON.parse(trimmed));
65
102
  savePolicy({
66
103
  Bucket: bucketName,
67
104
  Policy: minifiedPolicy
@@ -84,12 +121,14 @@ const BucketPolicyPage = ()=>{
84
121
  }, [
85
122
  bucketName,
86
123
  savePolicy,
124
+ deletePolicy,
87
125
  navigateToBucket,
88
126
  showToast,
89
127
  setError
90
128
  ]);
91
- const hasUnexpectedError = policyError && 'NoSuchBucketPolicy' !== policyError.name;
92
- if ('pending' === policyStatus || isISVLoading) return /*#__PURE__*/ jsx(Loader, {
129
+ if (isISVManaged || isISVLoading) return null;
130
+ const hasUnexpectedError = policyError && !isNotFoundError(policyError);
131
+ if ('pending' === policyStatus) return /*#__PURE__*/ jsx(Loader, {
93
132
  centered: true,
94
133
  size: "massive",
95
134
  children: /*#__PURE__*/ jsx(Fragment, {
@@ -139,7 +178,7 @@ const BucketPolicyPage = ()=>{
139
178
  label: "Cancel",
140
179
  onClick: navigateToBucket,
141
180
  type: "button",
142
- disabled: isSaving
181
+ disabled: isSaving || isDeleting
143
182
  }),
144
183
  /*#__PURE__*/ jsx(Button, {
145
184
  variant: "primary",
@@ -148,83 +187,80 @@ const BucketPolicyPage = ()=>{
148
187
  name: "Save"
149
188
  }),
150
189
  type: "submit",
151
- disabled: !isDirty && !isCreateMode || isSaving || !isValidFormat
190
+ disabled: !isDirty && !isCreateMode || isSaving || isDeleting || !isValidFormat
152
191
  })
153
192
  ]
154
193
  }),
155
- children: /*#__PURE__*/ jsxs(Fragment, {
194
+ children: /*#__PURE__*/ jsxs(FormSection, {
156
195
  children: [
157
- isISVManaged && /*#__PURE__*/ jsx(Banner, {
158
- variant: "warning",
159
- icon: /*#__PURE__*/ jsx(Icon, {
160
- color: "statusWarning",
161
- name: "Exclamation-circle"
162
- }),
163
- children: /*#__PURE__*/ jsxs(Text, {
164
- children: [
165
- /*#__PURE__*/ jsx("strong", {
166
- children: "Warning:"
167
- }),
168
- " This bucket is managed by ",
169
- isvApplication,
170
- ". Adding a Bucket Policy may conflict with IAM policies created by ",
171
- isvApplication,
172
- ", potentially disrupting ",
173
- isvApplication,
174
- " operations (e.g., backup cleanup). Ensure your Bucket Policy does not deny actions that ",
175
- isvApplication,
176
- " requires."
177
- ]
196
+ /*#__PURE__*/ jsx(FormGroup, {
197
+ id: "bucketName",
198
+ label: "Bucket Name",
199
+ required: true,
200
+ direction: "horizontal",
201
+ content: /*#__PURE__*/ jsx(Text, {
202
+ children: bucketName
178
203
  })
179
204
  }),
180
- /*#__PURE__*/ jsxs(FormSection, {
181
- children: [
182
- /*#__PURE__*/ jsx(FormGroup, {
183
- id: "bucketName",
184
- label: "Bucket Name",
185
- required: true,
186
- direction: "horizontal",
187
- content: /*#__PURE__*/ jsx(Text, {
188
- children: bucketName
189
- })
190
- }),
191
- /*#__PURE__*/ jsx(FormGroup, {
192
- id: "policyDocument",
193
- label: "Policy Document",
194
- required: true,
195
- direction: "vertical",
196
- help: "AWS Bucket policy standards are supported.",
197
- helpErrorPosition: "bottom",
198
- error: errors.content?.message,
199
- content: /*#__PURE__*/ jsxs(Stack, {
205
+ /*#__PURE__*/ jsx(FormGroup, {
206
+ id: "policyDocument",
207
+ label: "Policy Document",
208
+ required: true,
209
+ direction: "vertical",
210
+ help: "AWS Bucket policy standards are supported.",
211
+ helpErrorPosition: "bottom",
212
+ error: errors.content?.message,
213
+ content: /*#__PURE__*/ jsxs(Stack, {
214
+ direction: "horizontal",
215
+ gap: "r16",
216
+ style: {
217
+ alignItems: 'flex-start'
218
+ },
219
+ children: [
220
+ /*#__PURE__*/ jsx(Controller, {
221
+ control: control,
222
+ name: "content",
223
+ render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Editor, {
224
+ value: value,
225
+ onChange: (newValue)=>{
226
+ onChange(newValue);
227
+ if (errors.content) clearErrors('content');
228
+ },
229
+ language: jsonSchema ? {
230
+ name: 'json',
231
+ schema: jsonSchema
232
+ } : 'json',
233
+ height: "60vh",
234
+ width: "32rem"
235
+ })
236
+ }),
237
+ /*#__PURE__*/ jsxs(Stack, {
238
+ direction: "vertical",
239
+ gap: "r8",
200
240
  children: [
201
- /*#__PURE__*/ jsx(Controller, {
202
- control: control,
203
- name: "content",
204
- render: ({ field: { onChange, value } })=>/*#__PURE__*/ jsx(Editor, {
205
- value: value,
206
- onChange: (newValue)=>{
207
- onChange(newValue);
208
- if (errors.content) clearErrors('content');
209
- },
210
- language: "json",
211
- height: "60vh",
212
- width: "33rem",
213
- beforeMount: handleBeforeMount
214
- })
241
+ /*#__PURE__*/ jsx(CopyButton, {
242
+ textToCopy: content,
243
+ label: "Policy",
244
+ variant: "outline"
215
245
  }),
216
- /*#__PURE__*/ jsx(Box, {
217
- alignSelf: "baseline",
218
- children: /*#__PURE__*/ jsx(CopyButton, {
219
- textToCopy: content,
220
- label: "Policy",
221
- variant: "outline"
222
- })
246
+ isCreateMode && !content && /*#__PURE__*/ jsx(Text, {
247
+ variant: "Smaller",
248
+ color: "textSecondary",
249
+ children: "Empty bucket policy"
250
+ }),
251
+ isCreateMode && !content && /*#__PURE__*/ jsx(Button, {
252
+ variant: "outline",
253
+ label: "Load a template",
254
+ onClick: loadTemplate,
255
+ type: "button",
256
+ style: {
257
+ minWidth: '10rem'
258
+ }
223
259
  })
224
260
  ]
225
261
  })
226
- })
227
- ]
262
+ ]
263
+ })
228
264
  })
229
265
  ]
230
266
  })
@@ -1 +1,2 @@
1
+ export declare function DefaultReplicationDestinationFields(): import("react/jsx-runtime").JSX.Element;
1
2
  export declare function BucketReplicationFormPage(): import("react/jsx-runtime").JSX.Element;