Skip to content

Commit

Permalink
Add Delete and Empty Bucket modals to S3 Browser
Browse files Browse the repository at this point in the history
- Add Delete Bucket modal for bucket removal
- Add Empty Bucket modal for clearing bucket contents
- Implement success/failure alerts for operations
- Handle S3 API responses

Signed-off-by: Divyansh Kamboj <[email protected]>
  • Loading branch information
weirdwiz committed Nov 14, 2024
1 parent 2819f18 commit f5277c5
Show file tree
Hide file tree
Showing 10 changed files with 829 additions and 42 deletions.
17 changes: 17 additions & 0 deletions locales/en/plugin__odf-console.json
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,23 @@
"Organize objects within a bucket by creating virtual folders for easier management and navigation of objects.": "Organize objects within a bucket by creating virtual folders for easier management and navigation of objects.",
"Folders structure and group objects logically by using prefixes in object keys, without enforcing any physical hierarchy.": "Folders structure and group objects logically by using prefixes in object keys, without enforcing any physical hierarchy.",
"Folder name": "Folder name",
"Delete bucket permanently?": "Delete bucket permanently?",
"The bucket is being deleted. This may take a while.": "The bucket is being deleted. This may take a while.",
"Cannot delete this bucket: it is not empty": "Cannot delete this bucket: it is not empty",
"Bucket must be empty before it can be deleted. Use the <2>Empty bucket</2> configuration to erase all the contents i.e. <5>objects</5> of the bucket and try again.": "Bucket must be empty before it can be deleted. Use the <2>Empty bucket</2> configuration to erase all the contents i.e. <5>objects</5> of the bucket and try again.",
"Deleting a bucket cannot be undone.": "Deleting a bucket cannot be undone.",
"Bucket names are unique. If you delete a bucket, another S3 user can use the name.": "Bucket names are unique. If you delete a bucket, another S3 user can use the name.",
"Bucket name input": "Bucket name input",
"<0>To confirm deletion, type <1>{{bucketName}}</1>:</0>": "<0>To confirm deletion, type <1>{{bucketName}}</1>:</0>",
"Empty bucket permanently?": "Empty bucket permanently?",
"Emptying the bucket will permanentaly delete all objects. This action cannot be undone.": "Emptying the bucket will permanentaly delete all objects. This action cannot be undone.",
"Any objects added during this process may also be deleted. To prevent adding new objects during the emptying process, consider updating the bucket policy (through CLI).": "Any objects added during this process may also be deleted. To prevent adding new objects during the emptying process, consider updating the bucket policy (through CLI).",
"The bucket is being emptied. This may take a while to complete.": "The bucket is being emptied. This may take a while to complete.",
"Cannot empty bucket": "Cannot empty bucket",
"Check potential reasons": "Check potential reasons",
"Bucket emptying was not completed. Check for conflicts or permissions issues that are blocking this operation.": "Bucket emptying was not completed. Check for conflicts or permissions issues that are blocking this operation.",
"Successfully emptied bucket ": "Successfully emptied bucket ",
"Your bucket is now empty. If you want to delete this bucket, click Delete bucket": "Your bucket is now empty. If you want to delete this bucket, click Delete bucket",
"<0>To confirm deletion, type <1>{{delete}}</1> in the text input field.</0>": "<0>To confirm deletion, type <1>{{delete}}</1> in the text input field.</0>",
"Object name": "Object name",
"Delete object?": "Delete object?",
Expand Down
206 changes: 171 additions & 35 deletions packages/odf/components/s3-browser/bucket-overview/BucketOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import * as React from 'react';
import { BucketDetails } from '@odf/core/components/s3-browser/bucket-details/BucketDetails';
import {
EmptyBucketAlerts,
EmptyBucketResponse,
} from '@odf/core/modals/s3-browser/delete-and-empty-bucket/EmptyBucketModal';
import {
LazyEmptyBucketModal,
LazyDeleteBucketModal,
} from '@odf/core/modals/s3-browser/delete-and-empty-bucket/lazy-delete-and-empty-bucket';
import PageHeading from '@odf/shared/heading/page-heading';
import { useRefresh } from '@odf/shared/hooks';
import { ModalKeys, defaultModalMap } from '@odf/shared/modals/types';
import { S3Commands } from '@odf/shared/s3';
import { BlueSyncIcon } from '@odf/shared/status';
import { K8sResourceKind } from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
Expand All @@ -26,7 +35,7 @@ import { ActionsColumn, IAction } from '@patternfly/react-table';
import { PREFIX, BUCKETS_BASE_ROUTE } from '../../../constants';
import { NooBaaObjectBucketModel } from '../../../models';
import { getBreadcrumbs } from '../../../utils';
import { NoobaaS3Provider } from '../noobaa-context';
import { NoobaaS3Context, NoobaaS3Provider } from '../noobaa-context';
import { CustomActionsToggle } from '../objects-list';
import { ObjectListWithSidebar } from '../objects-list/ObjectListWithSidebar';
import { PageTitle } from './PageTitle';
Expand All @@ -47,16 +56,39 @@ const getBucketActionsItems = (
navigate: NavigateFunction,
bucketName: string,
isCreatedByOBC: boolean,
noobaaObjectBucket: K8sResourceKind
noobaaS3: S3Commands,
noobaaObjectBucket: K8sResourceKind,
refreshTokens: () => void,
setEmptyBucketResponse: React.Dispatch<
React.SetStateAction<EmptyBucketResponse>
>
): IAction[] => [
// ToDo: add empty/delete bucket actions
{
title: t('Empty bucket'),
onClick: () => undefined,
onClick: () =>
launcher(LazyEmptyBucketModal, {
isOpen: true,
extraProps: {
bucketName,
noobaaS3,
refreshTokens,
setEmptyBucketResponse,
},
}),
},
{
title: t('Delete bucket'),
onClick: () => undefined,
onClick: () =>
launcher(LazyDeleteBucketModal, {
isOpen: true,
extraProps: {
bucketName,
noobaaS3,
launcher,
refreshTokens,
setEmptyBucketResponse,
},
}),
},
...(isCreatedByOBC
? [
Expand Down Expand Up @@ -90,6 +122,53 @@ const getBucketActionsItems = (
: []),
];

const createBucketActions = (
t: TFunction,
fresh: boolean,
triggerRefresh: () => void,
foldersPath: string | null,
launcher: ReturnType<typeof useModal>,
navigate: NavigateFunction,
bucketName: string,
isCreatedByOBC: boolean,
noobaaS3: S3Commands,
noobaaObjectBucket: K8sResourceKind,
setEmptyBucketResponse: React.Dispatch<
React.SetStateAction<EmptyBucketResponse>
>
) => {
return (
<>
<Button
className="pf-v5-u-mr-md pf-v5-u-mb-xs"
variant={ButtonVariant.link}
icon={<BlueSyncIcon />}
onClick={triggerRefresh}
isDisabled={!fresh}
isInline
>
{t('Refresh')}
</Button>
{!foldersPath && (
<ActionsColumn
items={getBucketActionsItems(
t,
launcher,
navigate,
bucketName,
isCreatedByOBC,
noobaaS3,
noobaaObjectBucket,
triggerRefresh,
setEmptyBucketResponse
)}
actionsToggle={CustomActionsToggle}
/>
)}
</>
);
};

const BucketOverview: React.FC<{}> = () => {
const { t } = useCustomTranslation();
const [fresh, triggerRefresh] = useRefresh();
Expand All @@ -100,6 +179,12 @@ const BucketOverview: React.FC<{}> = () => {
const { bucketName } = useParams();
const [searchParams] = useSearchParams();

const [emptyBucketResponse, setEmptyBucketResponse] =
React.useState<EmptyBucketResponse>({
response: null,
bucketName: '',
});

// if non-empty means we are inside particular folder(s) of a bucket, else just inside a bucket (top-level)
const foldersPath = searchParams.get(PREFIX);

Expand Down Expand Up @@ -151,38 +236,84 @@ const BucketOverview: React.FC<{}> = () => {
: []),
];

const actions = () => {
return (
<>
<Button
className="pf-v5-u-mr-md pf-v5-u-mb-xs"
variant={ButtonVariant.link}
icon={<BlueSyncIcon />}
onClick={triggerRefresh}
isDisabled={!fresh}
isInline
>
{t('Refresh')}
</Button>
{!foldersPath && (
<ActionsColumn
items={getBucketActionsItems(
t,
launcher,
navigate,
bucketName,
isCreatedByOBC,
noobaaObjectBucket
)}
actionsToggle={CustomActionsToggle}
/>
)}
</>
const renderActions = (noobaaS3: S3Commands) => () =>
createBucketActions(
t,
fresh,
triggerRefresh,
foldersPath,
launcher,
navigate,
bucketName,
isCreatedByOBC,
noobaaS3,
noobaaObjectBucket,
setEmptyBucketResponse
);
};

return (
<NoobaaS3Provider loading={!objectBucketsLoaded} error={objectBucketsError}>
<BucketOverviewContent
breadcrumbs={breadcrumbs}
foldersPath={foldersPath}
currentFolder={currentFolder}
isCreatedByOBC={isCreatedByOBC}
noobaaObjectBucket={noobaaObjectBucket}
fresh={fresh}
triggerRefresh={triggerRefresh}
navPages={navPages}
bucketName={bucketName}
actions={renderActions}
launcher={launcher}
emptyBucketResponse={emptyBucketResponse}
setEmptyBucketResponse={setEmptyBucketResponse}
/>
</NoobaaS3Provider>
);
};

type NavPage = {
href: string;
name: string;
component: React.ComponentType<any>;
};

type BucketOverviewContentProps = {
breadcrumbs: { name: string; path: string }[];
foldersPath: string | null;
currentFolder: string;
isCreatedByOBC: boolean;
fresh: boolean;
triggerRefresh: () => void;
noobaaObjectBucket: K8sResourceKind;
navPages: NavPage[];
bucketName: string;
actions: (noobaaS3: S3Commands) => () => JSX.Element;
launcher: LaunchModal;
emptyBucketResponse: EmptyBucketResponse;
setEmptyBucketResponse: React.Dispatch<
React.SetStateAction<EmptyBucketResponse>
>;
};

const BucketOverviewContent: React.FC<BucketOverviewContentProps> = ({
breadcrumbs,
foldersPath,
currentFolder,
isCreatedByOBC,
fresh,
triggerRefresh,
noobaaObjectBucket,
navPages,
bucketName,
actions,
emptyBucketResponse,
setEmptyBucketResponse,
}) => {
const { noobaaS3 } = React.useContext(NoobaaS3Context);

return (
<>
<PageHeading
breadcrumbs={breadcrumbs}
title={
Expand All @@ -194,9 +325,14 @@ const BucketOverview: React.FC<{}> = () => {
noobaaObjectBucket={noobaaObjectBucket}
/>
}
actions={actions}
actions={actions(noobaaS3)}
className="pf-v5-u-mt-md"
/>
<EmptyBucketAlerts
emptyBucketResponse={emptyBucketResponse}
setEmptyBucketResponse={setEmptyBucketResponse}
triggerRefresh={triggerRefresh}
/>
<HorizontalNav
pages={navPages}
resource={
Expand All @@ -207,7 +343,7 @@ const BucketOverview: React.FC<{}> = () => {
} as any
}
/>
</NoobaaS3Provider>
</>
);
};

Expand Down
Loading

0 comments on commit f5277c5

Please sign in to comment.