Skip to content

Commit a8f96ff

Browse files
author
Adam Stafford
committed
delete multiple objects
1 parent 373d576 commit a8f96ff

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2021 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import React, { useState } from "react";
18+
import { connect } from "react-redux";
19+
import {
20+
Button,
21+
Dialog,
22+
DialogActions,
23+
DialogContent,
24+
DialogContentText,
25+
DialogTitle,
26+
LinearProgress,
27+
} from "@material-ui/core";
28+
import { setErrorSnackMessage } from "../../../../../../actions";
29+
import api from "../../../../../../common/api";
30+
31+
interface IDeleteObjectProps {
32+
closeDeleteModalAndRefresh: (refresh: boolean) => void;
33+
deleteOpen: boolean;
34+
selectedObjects: string[];
35+
selectedBucket: string;
36+
setErrorSnackMessage: typeof setErrorSnackMessage;
37+
}
38+
39+
const DeleteObject = ({
40+
closeDeleteModalAndRefresh,
41+
deleteOpen,
42+
selectedBucket,
43+
selectedObjects,
44+
setErrorSnackMessage,
45+
}: IDeleteObjectProps) => {
46+
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
47+
48+
const removeRecord = (i: number) => {
49+
if (deleteLoading) {
50+
return;
51+
}
52+
let recursive = false;
53+
if (selectedObjects[i].endsWith("/")) {
54+
recursive = true;
55+
}
56+
setDeleteLoading(true);
57+
api
58+
.invoke(
59+
"DELETE",
60+
`/api/v1/buckets/${selectedBucket}/objects?path=${selectedObjects[i]}&recursive=${recursive}`
61+
)
62+
.then(() => {
63+
setDeleteLoading(false);
64+
closeDeleteModalAndRefresh(true);
65+
})
66+
.catch((err) => {
67+
setDeleteLoading(false);
68+
setErrorSnackMessage(err);
69+
});
70+
};
71+
72+
return (
73+
<Dialog
74+
open={deleteOpen}
75+
onClose={() => {
76+
closeDeleteModalAndRefresh(false);
77+
}}
78+
aria-labelledby="alert-dialog-title"
79+
aria-describedby="alert-dialog-description"
80+
>
81+
<DialogTitle id="alert-dialog-title">Delete</DialogTitle>
82+
<DialogContent>
83+
{deleteLoading && <LinearProgress />}
84+
<DialogContentText id="alert-dialog-description">
85+
Are you sure you want to delete the selected objects?{" "}
86+
</DialogContentText>
87+
</DialogContent>
88+
<DialogActions>
89+
<Button
90+
onClick={() => {
91+
closeDeleteModalAndRefresh(false);
92+
}}
93+
color="primary"
94+
disabled={deleteLoading}
95+
>
96+
Cancel
97+
</Button>
98+
<Button
99+
onClick={() => {
100+
for (var i = 0; i < selectedObjects.length; i++) {
101+
removeRecord(i);
102+
}
103+
}}
104+
color="secondary"
105+
disabled={deleteLoading}
106+
>
107+
Delete
108+
</Button>
109+
</DialogActions>
110+
</Dialog>
111+
);
112+
};
113+
114+
const mapDispatchToProps = {
115+
setErrorSnackMessage,
116+
};
117+
118+
const connector = connect(null, mapDispatchToProps);
119+
120+
export default connector(DeleteObject);

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ import {
7575
} from "../../../../../../actions";
7676
import { BucketVersioning } from "../../../types";
7777
import RewindEnable from "./RewindEnable";
78+
import FileCopyIcon from "@material-ui/icons/FileCopy";
79+
import DeleteIcon from "@material-ui/icons/Delete";
80+
import DeleteMultipleObjects from "./DeleteMultipleObjects";
7881

7982
const commonIcon = {
8083
backgroundRepeat: "no-repeat",
@@ -226,6 +229,7 @@ const ListObjects = ({
226229
const [rewind, setRewind] = useState<RewindObject[]>([]);
227230
const [loadingRewind, setLoadingRewind] = useState<boolean>(true);
228231
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
232+
const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
229233
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
230234
const [selectedObject, setSelectedObject] = useState<string>("");
231235
const [selectedBucket, setSelectedBucket] = useState<string>("");
@@ -236,6 +240,7 @@ const ListObjects = ({
236240
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
237241
const [isVersioned, setIsVersioned] = useState<boolean>(false);
238242
const [rewindSelect, setRewindSelect] = useState<boolean>(false);
243+
const [selectedObjects, setSelectedObjects] = useState<string[]>([]);
239244

240245
const bucketName = match.params["bucket"];
241246

@@ -451,6 +456,16 @@ const ListObjects = ({
451456
}
452457
};
453458

459+
const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => {
460+
setDeleteMultipleOpen(false);
461+
462+
if (refresh) {
463+
setSnackBarMessage(`Objects deleted successfully.`);
464+
setSelectedObjects([]);
465+
setLoading(true);
466+
}
467+
};
468+
454469
const closeAddFolderModal = () => {
455470
setCreateFolderOpen(false);
456471
};
@@ -676,6 +691,25 @@ const ListObjects = ({
676691
}
677692
};
678693

694+
const selectListObjects = (e: React.ChangeEvent<HTMLInputElement>) => {
695+
const targetD = e.target;
696+
const value = targetD.value;
697+
const checked = targetD.checked;
698+
699+
let elements: string[] = [...selectedObjects]; // We clone the selectedBuckets array
700+
701+
if (checked) {
702+
// If the user has checked this field we need to push this to selectedBucketsList
703+
elements.push(value);
704+
} else {
705+
// User has unchecked this field, we need to remove it from the list
706+
elements = elements.filter((element) => element !== value);
707+
}
708+
setSelectedObjects(elements);
709+
710+
return elements;
711+
};
712+
679713
const listModeColumns = [
680714
{
681715
label: "Name",
@@ -737,6 +771,14 @@ const ListObjects = ({
737771
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
738772
/>
739773
)}
774+
{deleteMultipleOpen && (
775+
<DeleteMultipleObjects
776+
deleteOpen={deleteMultipleOpen}
777+
selectedBucket={selectedBucket}
778+
selectedObjects={selectedObjects}
779+
closeDeleteModalAndRefresh={closeDeleteMultipleModalAndRefresh}
780+
/>
781+
)}
740782
{createFolderOpen && (
741783
<CreateFolderModal
742784
modalOpen={createFolderOpen}
@@ -837,6 +879,17 @@ const ListObjects = ({
837879
style={{ display: "none" }}
838880
/>
839881
</Button>
882+
<Button
883+
variant="contained"
884+
color="primary"
885+
startIcon={<DeleteIcon />}
886+
onClick={() => {
887+
setDeleteMultipleOpen(true);
888+
}}
889+
disabled={selectedObjects.length === 0}
890+
>
891+
Delete Selected
892+
</Button>
840893
</Grid>
841894
<Grid item xs={12}>
842895
<br />
@@ -851,6 +904,8 @@ const ListObjects = ({
851904
idField="name"
852905
records={rewindEnabled ? rewind : filteredRecords}
853906
customPaperHeight={classes.browsePaper}
907+
selectedItems={selectedObjects}
908+
onSelect={selectListObjects}
854909
/>
855910
</Grid>
856911
</Grid>

0 commit comments

Comments
 (0)